并发爬取百度贴吧

撰写本文目的在于学习python的几个模块和多线程的一些知识
用到的python知识有:

  • requests
  • lxml
  • xpath (严格来不该成为模块, 是一种提取数据的方法)
  • multiprocess.dummy, Pool, 线程池
  • map 函数

本文分以下俩大部分介绍: 首先简单介绍以上各个知识, 最后以一则爬去百度贴吧回复的实例来演示

一. 各个知识简要介绍


1. requests

简介

虽然 python 已经有了 urllib 之类的网络库 但是,使用 requests 可以让 Web 客户端开发变得更加简单, 你可以阅读文档获取更多的信息。 本文会简单介绍 requests 的基本用法。
正如 Requests 的官方文档所说

  1. Requests 是使用 Apache2 Licensed 许可证的 HTTP 库。用 Python 编写,真正的为人类着想。
  2. Python 标准库中的 urllib2 模块提供了你所需要的大多数 HTTP 功能,但是它的 API 太渣了。它是为另一个时代、另一个互联网所创建的。它需要巨量的工作,甚至包括各种方法覆盖,来完成最简单的任务。在Python的世界里,事情不应该这么麻烦。
  3. Requests 使用的是 urllib3,因此继承了它的所有特性。Requests 支持 HTTP 连接保持和连接池,支持使用 cookie 保持会话,支持文件上传,支持自动确定响应内容的编码,支持国际化的 URL 和 POST 数据自动编码。现代、国际化、人性化。

下面我们来看看 Requests 有那些基本用法

发送 http 请求

支持的http 方法有 method = [get, post, put, delete, head, options]
所有的请求Api都是统一的requests.method(url), 如一个get请求:

>>> r = requests.get("http://www.baidu.com")

URL 传递参数

在 requests 中, 允许使用 params 关键字参数, 以一个字典来提供这些参数。如:
要请求的 url 为 http://www.baidu.com/get?key1=val1&key2=val2
则可以如下 构造代码:

>>> payload = {'key1': 'val1', 'key2': 'val2'}
>>> r = requests.get("http://www.baidu.com/get", params=payload)

获取响应的内容

>>> print r.content  ## 以字节的方式显示, 中文显示为字符
>>> print r.text  ## 以文本的方式去显示

Requests会自动解码来自服务器的内容。大多数unicode字符集都能被无缝地解码。

获取/修改网页编码

>>> r = requests.get('https://github.com/timeline.json')
>>> print r.encoding
'utf-8'

json处理

>>> r = requests.get('https://github.com/timeline.json')
>>> print r.json()

定制请求头

>>> url = 'http://www.baidu.com'
>>> headers = {'User-Agent' : 'Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 4 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19'}
>>> r = requests.post(url, headers=headers)
>>> print r.request.headers

复杂post请求

>>> url = 'http://www.baidu.com'
>>> payload = {'some': 'data'}
>>> r = requests.post(url, data=json.dumps(payload)) 
# 如果传递的payload是string而不是dict,需要先调用dumps方法格式化一下

响应状态码

>>> r = requests.get('http://www.baidu.com')
>>> print r.status_code

响应头

>>> r = requests.get('http://www.baidu.com')
>>> print r.headers
>>> print r.headers['Content-Type']
>>> print r.headers.get('content-type') #访问响应头部分内容的两种方式

Cookies

>>> url = 'http://example.com/some/cookie/setting/url'
>>> r = requests.get(url)
>>> r.cookies['example_cookie_name']    #读取cookies

>>> url = 'http://www.baidu.com/cookies'
>>> cookies = dict(cookies_are='working')
>>> r = requests.get(url, cookies=cookies) #发送cookies

#设置超时时间
>>> r = requests.get(‘http://www.baidu.com', timeout=0.001)

#设置访问代理
>>> proxies = {
“http”: “http://10.10.10.10:8888",
“https”: “http://10.10.10.100:4444",
}
>>> r = requests.get(‘http://www.baidu.com', proxies=proxies)

2. lxml

lxm是Python的一个html/xml解析并建立dom的库,lxml的特点是功能强大,性能也不错,xml包含了ElementTree ,html5lib ,beautfulsoup 等库,但是lxml也有自己相对应的库,所以,导致lxml比较复杂,初次使用者很难了解其关系。

本文我们只用到etree将html变成可xpath的对象, 有兴趣的同学可以查看文档进行深一步的学习lxml更多的功能。

3. Xpath

简介

XPath即为XML路径语言(XML Path Language),它是一种用来确定XML文档中某部分位置的语言。

XPath基于XML的树状结构,提供在数据结构树中找寻节点的能力。起初XPath的提出的初衷是将其作为一个通用的、介于XPointer与XSL间的语法模型。但是XPath很快的被开发者采用来当作小型查询语言。

我们都知道HMTL的结构是树形结构, 逐层展开的。
Xpath 正是基于 HTML 的这种结构来寻找其中的某个具体的节点的。

基本用法

下面让我们以一段例子来说明如何用 Xpath 寻找 html 中的信息

index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Xpath 学习</title>
</head>
<body>
<div id="content">

<ul id="useful">
<li>这是第一条信息</li>
<li>这是第二条信息</li>
<li>这是第三条信息</li>
</ul>

<ul id="useless">
<li>不需要的信息1</li>
<li>不需要的信息2</li>
<li>不需要的信息3</li>
</ul>

<div class="url">
<a href="http://www.baidu.com">百度</a>
<a href="http://www.google.com">Google</a>
</div>

<div id="test-1">需要的内容1</div>
<div id="test-2">需要的内容2</div>
<div id="testdefualt">需要的内容3</div>

<div class="nest">
Hi,
<p>美女,</p>
<span style="color: red">你的微信号是多少呀?</span>
</div>
</div>
</body>
</html>
code.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# coding:utf-8

from lxml import etree
import requests

with open('index.html') as f:
html = f.read()

selector = etree.HTML(html)

title = selector.xpath('//title/text()')[0]
usefulitems = selector.xpath('//ul[@id="useful"]/li/text()')
uselessitems = selector.xpath('//ul[@id="useless"]/li/text()')
urlitems = selector.xpath('//div[@class="url"]/a/@href')
startWithItems = selector.xpath('//div[starts-with(@id, "test")]/text()')
nest = selector.xpath('//div[@class="nest"]')[0].xpath('string(.)').replace(" ", "").replace("\n","")

print "title: \n\t", title
print "useful: \n\t", ",".join(usefulitems)
print "useless: \n\t", ",".join(uselessitems)
print "urls: \n\t", ",".join(urlitems)
print "startWithItems: \n\t", ",".join(startWithItems)
print "nest: \n\t", nest

如上11-16行主要为Xpath提取节点的过程, 通过代码我们可以看出一下规律

  1. 都是以//开头, 表示我们从整个html的根开始查找
  2. 所有html的标签直接写名字即可: 如: title, div, ul, a
  3. 所有的属性以@开头, 如: id, class
  4. text(), 提取标签下的文本
  5. starts-with, 用来提取以相同字符串开头的某些节点
  6. string(.), 用来提取那些嵌套在标签里的文本

以下是上述代码运行的结果:

title: 
    Xpath 学习
useful: 
    这是第一条信息,这是第二条信息,这是第三条信息
useless: 
    不需要的信息1,不需要的信息2,不需要的信息3
urls: 
    http://www.baidu.com,http://www.google.com
startWithItems: 
    需要的内容1,需要的内容2,需要的内容3
nest: 
    Hi,美女,你的微信号是多少呀?

二. 实例演示


实例代码

TiebaSpider.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# coding:utf-8
import requests
from multiprocessing.dummy import Pool as ThreadPool
from lxml import etree
import json
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

def writeFile(item):
with open("content.txt", 'a+') as f:
f.write(u'用户名: ' + item['user'] + "\t" + u'时间: ' + item['time'] + "\n")
f.write(u'内容: \n' + item['content'] + "\n")
f.write('-'*50 + "\n")

def spider(url):
print "downloading: ", url
html = requests.get(url)
selector = etree.HTML(html.text)
content_fields = selector.xpath('//div[@class="l_post j_l_post l_post_bright "]')
for each in content_fields:
reply_info = json.loads(each.xpath('@data-field')[0])

user = reply_info['author']['user_name']
time = reply_info['content']['date']
content = each.xpath('div[@class="d_post_content_main"]//div[@class="d_post_content j_d_post_content clearfix"]')[0].xpath("string(.)")

item = {}
item['user'] = user
item['time'] = time
item['content'] = content
writeFile(item)


if __name__ == '__main__':
urls = []
for i in xrange(1,10):
urls.append("http://tieba.baidu.com/p/4558076253?pn=%s"%i)

pool = ThreadPool()
pool.map(spider, urls)
pool.close()
pool.join()
print "done"

抓取结果

用户名: babylengyan    时间: 2016-05-20 00:17
内容: 
            抽不中
--------------------------------------------------
用户名: MiMi宝宝一    时间: 2016-05-20 00:17
内容: 
            好
--------------------------------------------------
用户名: 冬眠祭prince  时间: 2016-05-20 00:18
内容: 
            凑热闹
--------------------------------------------------
用户名: 独壹无贰婷女子    时间: 2016-05-20 00:23
内容: 
            我没看见
--------------------------------------------------
用户名: 独壹无贰婷女子    时间: 2016-05-20 00:24
内容: 
            谁看见我男友了吗
--------------------------------------------------
用户名: 独壹无贰婷女子    时间: 2016-05-20 00:24
内容: 
            让他快点来找我
--------------------------------------------------
用户名: 独壹无贰婷女子    时间: 2016-05-20 00:24
内容: 
            车费报销,
......