Python 爬虫第三步 -- 多线程爬虫爬取当当网书籍信息
XPath 的安装以及使用
1 . XPath 的介绍
只要开始学习正则表达式就会发现其应用起来非常顺利。现在决定完全摒弃这种做法,转而采用更为专业的工具——XPath 。有意见者认为这种做法实在不够高明,如果当初一开始就掌握这种更为专业的工具会事半功倍啊。其实我个人认为学习一下正则表达式是大有益处的,之所以换成 XPath ,我认为是因为它定位更准确,使用更加便捷。
有些人可能不清楚XPath与正则表达式之间的区别。比如用来提取位置内容时,在正则表达式中你更像是根据形状去寻找目标位置:左边是一个圆形建筑、右边是一个方形建筑,请自行寻找吧;而使用XPath时,则通过具体地址定位目标:位置描述变成了具体的天安门地址。那么怎样呢?哪种方法更为高效准确一些?
2 . XPath 的安装
XPath存在于lstm库中,请问从何处获取呢?访问这里后,请使用Ctrl+F查找lstm库并下载。在完成下载后,请将文件扩展名为.zip并执行解压操作。请将命名框为lstm*的文件夹拖放或复制粘贴至Python程序的Lib目录。这样就完成了安装过程。
3 . XPath 的使用
便于展示目的。
我开发了一个简单的网页。
如图所示:
其中一部分是为了节省时间...
方便小伙伴们直接进行测试...
可直接复制粘贴我的代码.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test Html</title>
</head>
<body>
<div id="content">
<ul id="like">
<li>like one</li>
<li>like two</li>
<li>like three</li>
</ul>
<ul id="hate">
<li>hate one</li>
<li>hate two</li>
<li>hate three</li>
</ul>
<div id="url">
<a href="http://www.baidu.com">百度一下</a>
<a href="http://www.hao123.com">好123</a>
</div>
</div>
</body>
</html>
用谷歌浏览器打开这个网页,然后右击,选择检查,会出现如下所示界面

当您右键点击任意一个HTML代码时,“Copy XPath 按钮就会显现出来。请先单击此按钮进行复制操作,“然后您就可以方便地使用这个功能了。
# coding=utf-8
from lxml import etree
f = open('myHtml.html','r')
html = f.read()
f.close()
selector = etree.HTML(html)
content = selector.xpath('//*[@id="like"]/li/text()')
for each in content:
print each
看看打印结果
like one
like two
like three
很显而易见地发现一个问题:在 XPath 表达式中调用了相应的功能来获取所需内容。值得注意的是,在 XPath 表达式中调用 text() 函数用于提取文本内容。然而该函数的作用即是提取文本内容而非其他用途。但如果我们需要获取某个属性怎么办?例如,在 HTML 页面中我们希望提取两个链接地址(即 href 属性),则可以通过以下步骤进行操作
content = selector.xpath('//*[@id="url"]/a/@href')
for each in content:
print each
这个时候的打印结果就是
http://www.baidu.com
http://www.hao123.com
如今大家大致已经掌握了xpath()函数中各个符号的基本含义。例如,在早期阶段,“//”这一符号就代表着整个文档的根目录位置;而像 '/' 这样的符号则表示当前节点下的直接子节点位置;其他属性如 'id' 则会逐层向下查找以确定其具体位置。考虑到XPath基于树状数据模型这一特点,在命名时自然采用了与之相关的名称etree()。
4 . XPath 的特殊用法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="likeone">like one</div>
<div id="liketwo">like two</div>
<div id="likethree">like three</div>
</body>
</html>
面对这个网页, 我们应该如何提取这三行内容呢? 嗯哼, 其实很简单, 只需编写三个 XPath 表达式即可完成任务, 太容易了。 如果真的如此的话, 那么我们的工作效率确实不高, 因为观察这三行 div 元素的 id 属性会发现它们前面都有 'like' 字符, 那么好办多了, 我们就可以利用 starts-with 函数对它们进行统一提取了, 如下所示
content = selector.xpath('//div[starts-with(@id,"like")]/text()')
这会带来一定的不便。因此我们需要手动编写相应的 XPath 表达式。当然还可以直接复制粘贴该字段到编辑器中进行调整。这种做法本质上是为了提高效率而牺牲便利性。接下来我们来分析一下标签之间的嵌套关系。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="content">
<div id="text">
<p>hello
<b> world
<font color="#ffe4c4">
Python
</font>
</b>
</p>
</div>
</div>
</body>
</html>
在类似的网页中,在想定位到hello world Python语句的时候怎么办?显然这是一个包含嵌套标签的情况,在正常情况下进行提取效果如何?
content = selector.xpath('//*[@id="text"]/p/text()')
for each in content:
print each
运行后感到有些惋惜地发现只有 hello 这个字符串被成功打印出来其余字符却缺失掉了。
有什么解决办法吗?
我们可以调用函数 string(.) 来实现这一目标如图 1 所示
content = selector.xpath('//*[@id="text"]/p')[0]
info = content.xpath('string(.)')
data = info.replace('\n','').replace(' ','')
print data
现在可以打印出正确的信息了。至于第三行的内容目前尚不清楚,请尝试删除这一行并观察结果如何;通过观察结果你很快就能明白原因。
Python 并行化的简单介绍
有观点认为在Python环境中所谓的并行化并非真正意义上的 concurrent execution。尽管如此,在多数实际应用中,通过使用多线程确实可以有效提升程序运行效率。然而,在多数实际应用中,通过使用多线程确实可以有效提升程序运行效率。从而将总运算时间减少约三分之一至四分之三。为了深入探讨这一现象的影响范围,在本节中我们将系统地对比分析单线程与多线程在性能指标上的差异。
# coding=utf-8
import requests
from multiprocessing.dummy import Pool as ThreadPool
import time
def getsource(url):
html = requests.get(url)
if __name__ == '__main__':
urls = []
for i in range(50, 500, 50):
newpage = 'http://tieba.baidu.com/f?kw=python&ie=utf-8&pn=' + str(i)
urls.append(newpage)
# 单线程计时
time1 = time.time()
for i in urls:
print i
getsource(i)
time2 = time.time()
print '单线程耗时 : ' + str(time2 - time1) + ' s'
# 多线程计时
pool = ThreadPool(4)
time3 = time.time()
results = pool.map(getsource, urls)
pool.close()
pool.join()
time4 = time.time()
print '多线程耗时 : ' + str(time4 - time3) + ' s'
打印结果为
http://tieba.baidu.com/f?kw=python&ie=utf-8&pn=50
http://tieba.baidu.com/f?kw=python&ie=utf-8&pn=100
http://tieba.baidu.com/f?kw=python&ie=utf-8&pn=150
http://tieba.baidu.com/f?kw=python&ie=utf-8&pn=200
http://tieba.baidu.com/f?kw=python&ie=utf-8&pn=250
http://tieba.baidu.com/f?kw=python&ie=utf-8&pn=300
http://tieba.baidu.com/f?kw=python&ie=utf-8&pn=350
http://tieba.baidu.com/f?kw=python&ie=utf-8&pn=400
http://tieba.baidu.com/f?kw=python&ie=utf-8&pn=450
单线程耗时 : 7.26399993896 s
多线程耗时 : 2.49799990654 s
关于为何将该链接设置为每页间隔50的原因在于我发现,在百度贴吧中未翻阅一页时pn字段的值随之增加50个单位。从上述实验结果可以看出,在不同条件下对比运行后发现,在单ithread版本中的性能表现并不理想;相比之下,在多ithread实现中效率提升显著;就不再深入讲解这部分内容;相信那些熟悉Java语言的人对于多ithread的基本用法都有大致了解;如果读者尚未接触过Java语言,则可能需要自行查阅相关资料以理解这些代码的功能与实现逻辑
实战 – 爬取当当网书籍信息
长期在京东平台采购书籍,在掌握了通过 Python 获取数据的技术后,请开始从京东网站获取所需数据的具体操作步骤如下:具体操作步骤如下:

在当当网上搜索关键词'Java'时返回了89个网页结果。随后我决定获取其中前80页的数据。为了对比多线程与单线程的工作效率,在这部分代码中我对两者进行了性能测试比较。测试结果显示,在处理相同规模的任务时:其中单线程完成任务所需时间为67秒,在采用多线程策略下仅需15秒。
如何获取网页数据,在之前关于 XPath 使用的介绍中也提到了相关内容。具体来说就是要进入目标网站页面并选择查看选项,在该页面的源代码文档中找到相应的标签结构后就可以提取所需信息了,在此不再赘述因为这段代码相对简短在这里直接提供源代码
# coding=utf8
import requests
import re
import time
from lxml import etree
from multiprocessing.dummy import Pool as ThreadPool
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
def changepage(url, total):
urls = []
nowpage = int(re.search('(\d+)', url, re.S).group(1))
for i in range(nowpage, total + 1):
link = re.sub('page_index=(\d+)', 'page_index=%s' % i, url, re.S)
urls.append(link)
return urls
def spider(url):
html = requests.get(url)
content = html.text
selector = etree.HTML(content)
title = []
title = selector.xpath('//*[@id="component_0__0__6612"]/li/a/@title')
detail = []
detail = selector.xpath('//*[@id="component_0__0__6612"]/li/p[3]/span[1]/text()')
saveinfo(title,detail)
def saveinfo(title, detail):
length1 = len(title)
for i in range(0, length1 - 1):
f.writelines(title[i] + '\n')
f.writelines(detail[i] + '\n\n')
if __name__ == '__main__':
pool = ThreadPool(4)
f = open('info.txt', 'a')
url = 'http://search.dangdang.com/?key=Java&act=input&page_index=1'
urls = changepage(url, 80)
time1 = time.time()
pool.map(spider, urls)
pool.close()
pool.join()
f.close()
print '爬取成功!'
time2 = time.time()
print '多线程耗时 : ' + str(time2 - time1) + 's'
# time1 = time.time()
# for each in urls:
# spider(each)
# time2 = time.time()
# f.close()
# print '单线程耗时 : ' + str(time2 - time1) + 's'
该段代码涉及的知识点非常丰富,在此基础之上,我们对XPath和并行化技术的相关知识进行了详尽阐述,并且能够确保整体阅读体验极佳。
好了,到今天为止,Python 爬虫相关系列的文章到此结束,谢谢你的观看。
