网络爬虫(Web Spiders)是一个自动提取网页的程序,它为搜索引擎从万维网上下载网页,是搜索引擎的重要组成。半年前,我接触了Lucene搜索引擎开发——开始了网络爬虫之旅;当时,采用的是纯JAVA开发环境,对百度某吧几百万帖子进行了全面爬取。那时候我对Python一点都不了解,今天对《Pyhon基础教程:第2版·修订版》以及极客学院相关视频进行了学习,形成了本文。
Key Words: 网络爬虫, 多线程, requests, XPath, lxml
1. 本文相关环境
开发工具与平台:Python2.7, Windows7, PyCharm
Python第三方包下载平台:http://www.lfd.uci.edu/~gohlke/pythonlibs/,该平台可以下载几乎所有Python包,包括本文的requests,lxml,下载之后如何安装(见下文)。另外,推荐一款json格式化工具:HiJson。
2. Windows平台,如何安装requests、lxml包?
从上述网站上,选择合适自己的版本(Ctrl+F,可搜索),下载完成后:
①将后缀“.whl”更改为“.zip”;
②解压,将“非-info”文件夹拷贝至“D:\AppInstall\Python27\Lib”即可。
3.requests简单介绍
- HTTP for humans.
- All the cool kids are doing it. Requests is one of the most downloaded Python packages of all time.
以上是官网上的两句原话:①人性化的HTTP; ②下载量最大的第三方包之一。事实上,requests确实很人性化,比如获取网页源代码如此简单:
GET方法:requests.get(url=, params=, kwargs=)
import requests
html = requests.get('http://tieba.baidu.com/f?ie=utf-8&kw=python')
print (html.text)
当然,有时候我们还需要伪装一下:
User-Agent面纱:审查元素->Network->刷新页面->选择某文件->headers
import requests
import re
url = 'http://tieba.baidu.com/f?ie=utf-8&kw=python'
ua = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'}
#GET请求数据
html = requests.get(url,headers=ua)
html.encoding = 'utf-8'
#正则匹配
title = re.findall('class="j_th_tit ">(.*?)</a>',html.text,re.S)
for each in title:
print(each)
GET并不是万能的,有些网页需要我们向其提交数据,才能获得反馈:
POST方法:requests.post(url=, data=, json=, kwargs=)
#构造POST字典
node = {
'enter':'true',
'page':'3'
}
#POST请求数据
html = requests.post(url,data=node)
4. XPath教程
- XPath 是一门在 XML 文档中查找信息的语言。XPath 可用来在 XML 文档中对元素和属性进行遍历。
- 具体查看W3C,http://www.w3school.com.cn/xpath/index.asp
XPath将我们从复杂的正则中解脱了出来,并且Chrome直接提供了该工具:
构造贴吧XPath:借助Chrome XPath着手,形成贴吧相应匹配
#构造XPath,正则每层楼内容
content_field = selector.xpath('//div[@class="l_post j_l_post l_post_bright "]')
#抓取data-field里类json数据,并去掉双引号,最后转为字典格式
data_field = json.loads(each.xpath('@data-field')[0].replace('"',''))
#回复内容
content = each.xpath('div[@class="d_post_content_main"]/div/cc/div[@class="d_post_content j_d_post_content clearfix"]/text()')[0]
一些注意事项,由于Python3强制向后不兼容,导致了可能出现以下问题:
- from lxml import etree,引入失败。解决方案:①换个lxml版本包;②换成Python2.7
- 如果不使用requests,而使用from urllib import urlopen;urllib.urlopen(tempUrl),出错。解决方案:import urllib.request;urllib.request.urlopen(tempUrl)
5. Python多线程
这个实现起来尤为简单,主要分为以下几步:
①导包:from multiprocessing.dummy import Pool as ThreadPool
②多线程,本机为2核CPU:pool = ThreadPool(2)
③map Python并发:pool.map(spider,page); pool.close(); pool.join()
6. 百度贴吧爬虫
根据以上介绍,一款贴吧爬虫呼之欲出,这里对上文图中data-field字段,格式化为json如下:
可以根据自己的需要,获取字段中更多的内容~
#-*-coding:utf8-*-
from multiprocessing.dummy import Pool as ThreadPool
from lxml import etree
import requests
import json
import sys
#强制编码,注意顶部声明
reload(sys)
sys.setdefaultencoding('utf-8')
#控制台显示
def showContent(item):
print(u'回帖时间:'+str(item['time'])+'\n')
print(u'回帖内容:'+unicode(item['content'])+'\n')
print(u'回帖人:'+item['user_name']+'\n\n')
#保存文件
def saveFile(item):
#u'',设定Unicode编码
f.writelines(u'回帖时间:'+str(item['time'])+'\n')
f.writelines(u'回帖内容:'+unicode(item['content'])+'\n')
f.writelines(u'回帖人:'+item['user_name']+'\n\n')
#网络爬虫
def spider(url):
#抓取网页源码
html = requests.get(url)
selector = etree.HTML(html.text)
#构造XPath,正则每层楼内容
content_field = selector.xpath('//div[@class="l_post j_l_post l_post_bright "]')
item = {}
for each in content_field:
#抓取data-field里类json数据,并去掉双引号,最后转为字典格式
data_field = json.loads(each.xpath('@data-field')[0].replace('"',''))
#用户名
user_name = data_field['author']['user_name']
#回复内容
content = each.xpath('div[@class="d_post_content_main"]/div/cc/div[@class=\
"d_post_content j_d_post_content clearfix"]/text()')[0]
#回复时间
time = data_field['content']['date']
#封装item[]
item['user_name'] = user_name
item['content'] = content
item['time'] = time
showContent(item)
saveFile(item)
if __name__ == '__main__':
#多线程,2核CPU
pool = ThreadPool(2)
#r'',避免转义,文件以追加形式打开,不存在就新建
f = open(r'D:\content.txt','a')
page = []
for i in range(1,21):
newpage = 'http://tieba.baidu.com/p/4880125218?pn='+str(i)
page.append(newpage)
#map实现Python多线程:map(爬取函数,网址列表)
results = pool.map(spider,page)
pool.close()
pool.join()
f.close() #关闭文件