用Python写网络爬虫.pdf
http://www.100md.com
2020年2月7日
![]() |
| 第1页 |
![]() |
| 第6页 |
![]() |
| 第13页 |
![]() |
| 第23页 |
![]() |
| 第43页 |
![]() |
| 第195页 |
参见附件(7403KB,261页)。
用Python写网络爬虫是作家Richard Lawson写的关于python的书籍,讲述了怎样通过pthon进行简单地编程语言,写出网络爬虫,爬取网络数据,并有案例分析。

用Python写网络爬虫内容简介
作为一种便捷地收集网上信息并从中抽取出可用信息的方式,网络爬虫技术变得越来越有用。使用Python这样的简单编程语言,你可以使用少量编程技能就可以爬取复杂的网站。 《用Python写网络爬虫》作为使用Python来爬取网络数据的杰出指南,讲解了从静态页面爬取数据的方法以及使用缓存来管理服务器负载的方法。此外,本书还介绍了如何使用AJAX URL和Firebug扩展来爬取数据,以及有关爬取技术的更多真相,比如使用浏览器渲染、管理cookie、通过提交表单从受验证码保护的复杂网站中抽取数据等。本书使用Scrapy创建了一个高级网络爬虫,并对一些真实的网站进行了爬取。
用Python写网络爬虫作者资料
Richard Lawson来自澳大利亚,毕业于墨尔本大学计算机科学专业。毕业后,他创办了一家专注于网络爬虫的公司,为超过50个国家的业务提供远程工作。他精通于世界语,可以使用汉语和韩语对话,并且积极投身于开源软件。他目前在牛津大学攻读研究生学位,并利用业余时间研发自主无人机。
用Python写网络爬虫精彩内容
无论如何,当你抓取某个网站的数据时,请记住自己是该网站的访客,应当约束自己的抓取行为,否则他们可能会封禁你的IP,甚至采取更进一步的法律行动。这就要求请求的速度需要限定在一个合理值之内,并且还需要设定一个专属的用户代理来标识自己。在下面的小节中我们将会对这些实践进行具体介绍。
用Python写网络爬虫截图


书名:用Python写网络爬虫
ISBN:978-7-115-43179-0
本书由人民邮电出版社发行数字版。版权所
有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使
用,未经授权,不得以任何方式复制和传播本书内
容。
我们愿意相信读者具有这样的良知和觉悟,与
我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实
施包括但不限于关闭该帐号等维权措施,并可能追
究法律责任。
著 [澳] Richard Lawson
译 李 斌 责任编辑 傅道坤
人民邮电出版社出版发行 北京市丰台
区成寿寺路11号
邮编 100164 电子邮件
315@ptpress.com.cn
网址 http:www.ptpress.com.cn
读者服务热线:(010)81055410
反盗版热线:(010)81055315版权声明
Copyright ? 2015 Packt Publishing. First
published in the English language under the title Web
Scraping with Python.
All Rights Reserved.
本书由英国Packt Publishing公司授权人民邮电
出版社出版。未经出版者书面许可,对本书的任何
部分不得以任何方式或任何手段复制和传播。
版权所有,侵权必究。内容提要
本书讲解了如何使用Python来编写网络爬虫程
序,内容包括网络爬虫简介,从页面中抓取数据的
三种方法,提取缓存中的数据,使用多个线程和进
程来进行并发抓取,如何抓取动态页面中的内容,与表单进行交互,处理页面中的验证码问题,以及
使用Scarpy和Portia来进行数据抓取,并在最后使用
本书介绍的数据抓取技术对几个真实的网站进行了
抓取,旨在帮助读者活学活用书中介绍的技术。
本书适合有一定Python编程经验,而且对爬虫
技术感兴趣的读者阅读。关于作者
Richard Lawson来自澳大利亚,毕业于墨尔本
大学计算机科学专业。毕业后,他创办了一家专注
于网络爬虫的公司,为超过50个国家的业务提供远
程工作。他精通于世界语,可以使用汉语和韩语对
话,并且积极投身于开源软件。他目前在牛津大学
攻读研究生学位,并利用业余时间研发自主无人
机。
我要感谢Timothy Baldwin教授将我引入这
个令人兴奋的领域,以及本书编写时在巴黎招
待我的Tharavy Douc。关于审稿人
Martin Burch是一名常驻纽约的数据记者,其
工作是为华尔街日报绘制交互式图表。他在新墨西
哥州立大学获得了新闻学和信息系统专业的学士学
位,然后在纽约城市大学新闻学研究院获得了新闻
学专业硕士学位。
我要感谢我的妻子Lisa鼓励我协助本书的
创作,我的叔叔Michael耐心解答我的编程问
题,以及我的父亲Richard激发了我对新闻学和
写作的热爱。
William Sankey是一位数据专业人士,也是一
位业余开发人员,生活在马里兰州科利奇帕克市。
他于2012年毕业于约翰?霍普金斯大学,获得了公
共政策硕士学位,专业方向为定量分析。他目前在
LM政策研究有限责任公司担任健康服务研究员,从事与美国医疗保险和医疗补助服务中心(CMS)
相关的项目。这些项目包括责任医疗机构评估以及
精神病院住院患者预付费系统监测。我要感谢我深爱的妻子Julia和顽皮的小猫
Ruby,给予我全部的爱和支持。
Ayush Tiwari是一名Python开发者,本科就读
于印度理工学院罗克分校。他自2013年起工作于印
度理工学院罗克分校信息管理小组,并活跃于网络
开发领域。对他而言,审阅本书是一个非常棒的经
历。他不仅是一名审稿人,也是一名狂热的网络爬
虫学习者。他向所有Python爱好者推荐本书,以便
享受爬虫的益处。 他热衷于Python网络爬虫,曾参
与体育直播订阅、通用Python电子商务网络爬虫
(在Miranj)等相关项目。 他还使用Django应用开
发了就业门户,帮助改善印度理工学院罗克分校的
就业流程。 除了后端开发之外,他还喜欢使用诸如
NumPy、SciPy等Python库进行科学计算和数据分
析,目前他从事计算流体力学领域的研究。你可以
在GitHub上访问到他的项目,他的用户名是
tiwariayush。 他喜欢徒步穿越喜马拉雅山谷,每年
会参加多次徒步行走活动。此外,他还喜欢弹吉
他。他的成就还包括参加国际知名的Super 30小
组,并在其中成为排名保持者。他在高中时,还参
加了国际奥林匹克数学竞赛。我的家庭成员(我的姐姐Aditi、我的父母以及
Anand先生)、我在VI和IMG的朋友以及我的教授
都为我提供了很大的帮助。我要感谢他们所有人对
我的支持。
最后,感谢尊敬的作者和Packt出版社团队出版
了这些非常好的技术书籍。我要对他们在编写这些
书籍时的所有辛苦工作表示赞赏。前言
互联网包含了迄今为止最有用的数据集,并且
大部分可以免费公开访问。但是,这些数据难以复
用。它们被嵌入在网站的结构和样式当中,需要抽
取出来才能使用。从网页中抽取数据的过程又被称
为网络爬虫。随着越来越多的信息被发布到网络
上,网络爬虫也变得越来越有用。阅读本书的前提
本书中所有的代码都已经在Python 2.7环境中
进行过测试,并且可以从
http:bitbucket.orgwswpcode下载到这些源代
码。理想情况下,本书未来的版本会将示例代码移
植到Python 3当中。不过,现在依赖的很多库(比
如ScrapyTwisted、Mechanize和Ghost)还只支持
Python 2。为了帮助阐明爬取示例,我们创建了一
个示例网站,其网址为http:example.
webscraping.com。由于该网站限制了下载内容的
速度,因此如果你希望自行搭建示例网站,可以从
http:bitbucket.orgwswpplaces获取网站源代
码和安装说明。
我们决定为本书中使用的大部分示例搭建一个
定制网站,而不是抓取活跃网站,这样我们就对环
境拥有了完全控制。这种方式为我们提供了稳定
性,因为活跃网站要比书中的定制网站更新更加频
繁,并且当你尝试运行爬虫示例时,代码可能已经
无法工作。另外,定制网站允许我们自定义示例,用于阐释特定技巧并避免其他干扰。最后,活跃网
站可能并不欢迎我们使用它作为学习网络爬虫的对
象,并且可能会尝试封禁我们的爬虫。使用我们自己定制的网站可以规避这些风险,不过在这些例子
中学到的技巧确实也可以应用到这些活跃网站当
中。本书读者
阅读本书需要有一定的编程经验,并且不适用
于绝对的初学者。在实践中,我们将会首先实现我
们自己的网络爬虫技术版本,然后才会介绍现有的
流行模块,这样可以让你更好地理解这些技术是如
何工作的。本书中的这些示例将假设你已经拥有
Python语言以及使用pip安装模块的能力。如果你
想复习一下这些知识,有一本非常好的免费在线书
籍可以使用,其作者为Mark Pilgrim,书籍网址
是http:www.diveintopython.net。这本书也是
我初学Python时所使用的资源。
此外,这些例子还假设你已经了解网页是如何
使用HTML进行构建并通过JavaScript更新的知识。
关于HTTP、CSS、AJAX、WebKit以及MongoDB的
既有知识也很有用,不过它们不是必需的,这些技
术会在需要使用时进行介绍。上述很多主题的详细
参考资料可以从http:www.w3schools.com获取
到。第1章 网络爬虫简介
本章中,我们将会介绍如下主题:
网络爬虫领域简介;
解释合法性质疑;
对目标网站进行背景调研;
逐步完善一个高级网络爬虫。1.1 网络爬虫何时有用
假设我有一个鞋店,并且想要及时了解竞争对
手的价格。我可以每天访问他们的网站,与我店铺
中鞋子的价格进行对比。但是,如果我店铺中的鞋
类品种繁多,或是希望能够更加频繁地查看价格变
化的话,就需要花费大量的时间,甚至难以实现。
再举一个例子,我看中了一双鞋,想等它促销时再
购买。我可能需要每天访问这家鞋店的网站来查看
这双鞋是否降价,也许需要等待几个月的时间,我
才能如愿盼到这双鞋促销。上述这 两个重复性的手
工流程,都可以利用本书介绍的网络爬虫技术实现
自动化处理。
理想状态下,网络爬虫并不是必须品,每个网
站都应该提供API,以结构化的格式共享它们的数
据。然而现实情况中,虽然一些网站已经提供了这
种API,但是它们通常会限制可以抓取的数据,以
及访问这些数据的频率。另外,对于网站的开发者
而言,维护前端界面比维护后端API接口优先级更
高。总之,我们不能仅仅依赖于API去访问我们所
需的在线数据,而是应该学习一些网络爬虫技术的
相关知识。1.2 网络爬虫是否合法
网络爬虫目前还处于早期的蛮荒阶段,“允许
哪些行为”这种基本秩序还处于建设之中。从目前
的实践来看,如果抓取数据的行为用于个人使用,则不存在问题;而如果数据用于转载,那么抓取的
数据类型就非常关键了。
世界各地法院的一些案件可以帮助我们确定哪
些网络爬虫行为是允许的。在Feist Publications,Inc.起诉Rural Telephone Service Co.的案件中,美国
联邦最高法院裁定抓取并转载真实数据(比如,电
话清单)是允许的。而在澳大利亚,Telstra
Corporation Limited起诉Phone Directories Company
Pty Ltd这一类似案件中,则裁定只有拥有明确作者
的数据,才可以获得版权。此外,在欧盟的ofir.dk
起诉home.dk一案中,最终裁定定期抓取和深度链
接是允许的。
这些案件告诉我们,当抓取的数据是现实生活
中的真实数据(比如,营业地址、电话清单)时,是允许转载的。但是,如果是原创数据(比如,意
见和评论),通常就会受到版权限制,而不能转
载。无论如何,当你抓取某个网站的数据时,请记
住自己是该网站的访客,应当约束自己的抓取行
为,否则他们可能会封禁你的IP,甚至采取更进一
步的法律行动。这就要求下载请求的速度需要限定
在一个合理值之内,并且还需要设定一个专属的用
户代理来标识自己。在下面的小节中我们将会对这
些实践进行具体介绍。
关于上述几个法律案件的更多信息可以参考下述地址:
http:caselaw.lp.findlaw.comscriptsgetcase. pl?
court=USvol=499invol=340
http:www.austlii.edu.auaucasescthFCA201044.html
http:www.bvhd.dkuploadstx_mocarticlesS_og_Handelsrettens_afg_relse_i_Ofir-
sagen.pdf1.3 背景调研
在深入讨论爬取一个网站之前,我们首先需要
对目标站点的规模和结构进行一定程度的了解。网
站自身的robots.txt和Sitemap文件都可以为我们
提供一定的帮助,此外还有一些能提供更详细信息
的外部工具,比如Google搜索和WHOIS。
1.3.1 检查robots.txt
大多数网站都会定义robots.txt文件,这样可
以让爬虫了解爬取该网站时存在哪些限制。这些限
制虽然仅仅作为建议给出,但是良好的网络公民都
应当遵守这些限制。在爬取之前,检查robots.txt
文件这一宝贵资源可以最小化爬虫被封禁的可能,而且还能发现和网站结构相关的线索。关于
robots.txt协议的更多信息可以参见
http:www.robotstxt.org。下面的代码是我们的
示例文件robots.txt中的内容,可以访问
http:example.webscraping.comrobots.txt获
取。
section 1
User-agent: BadCrawler
Disallow: section 2
User-agent:
Crawl-delay: 5
Disallow: trap
section 3
Sitemap: http:example.webscraping.comsitemap.xml
在section 1中,robots.txt文件禁止用户代
理为BadCrawler的爬虫爬取该网站,不过这种写法
可能无法起到应有的作用,因为恶意爬虫根本不会
遵从robots.txt的要求。本章后面的一个例子将会
展示如何让爬虫自动遵守robots.txt的要求。
section 2规定,无论使用哪种用户代理,都
应该在两次下载请求之间给出5秒的抓取延迟,我
们需要遵从该建议以避免服务器过载。这里还有一
个trap链接,用于封禁那些爬取了不允许链接的
恶意爬虫。如果你访问了这个链接,服务器就会封
禁你的IP一分钟!一个真实的网站可能会对你的IP
封禁更长时间,甚至是永久封禁。不过如果这样设
置的话,我们就无法继续这个例子了。
section 3定义了一个Sitemap文件,我们将在
下一节中了解如何检查该文件。
1.3.2 检查网站地图
网站提供的Sitemap文件(即网站地图)可以帮助爬虫定位网站最新的内容,而无须爬取每一个
网页。如果想要了解更多信息,可以从
http:www.sitemaps.orgprotocol.html获取网站
地图标准的定义。下面是在robots.txt文件中发现
的Sitemap文件的内容。
http:example.webscraping.comviewAfghanistan-1
http:example.webscraping.comviewAland-Islands-2
http:example.webscraping.comviewAlbania-3...
网站地图提供了所有网页的链接,我们会在后
面的小节中使用这些信息,用于创建我们的第一个
爬虫。虽然Sitemap文件提供了一种爬取网站的有
效方式,但是我们仍需对其谨慎处理,因为该文件
经常存在缺失、过期或不完整的问题。
1.3.3 估算网站大小
目标网站的大小会影响我们如何进行爬取。如
果是像我们的示例站点这样只有几百个URL的网
站,效率并没有那么重要;但如果是拥有数百万个
网页的站点,使用串行下载可能需要持续数月才能
完成,这时就需要使用第4章中介绍的分布式下载来解决了。
估算网站大小的一个简便方法是检查Google爬
虫的结果,因为Google很可能已经爬取过我们感兴
趣的网站。我们可以通过Google搜索的site关键词
过滤域名结果,从而获取该信息。我们可以从
http:www.google.comadvanced_search了解到该
接口及其他高级搜索参数的用法。
图1.1所示为使用site关键词对我们的示例网站进行
搜索的结果,即在Google中搜
索site:example.webscraping.com。
从图1.1中可以看出,此时Google估算该网站拥
有202个网页,这和实际情况差不多。不过对于更
大型的网站,我们会发现Google的估算并不十分准
确。
在域名后面添加URL路径,可以对结果进行过
滤,仅显示网站的某些部分。图1.2所示为搜
索site:example.webscraping.comview的结果。该
搜索条件会限制Google只搜索国家页面。图1.1图1.2
这种附加的过滤条件非常有用,因为在理想情
况下,你只希望爬取网站中包含有用数据的部分,而不是爬取网站的每个页面。
1.3.4 识别网站所用技术
构建网站所使用的技术类型也会对我们如何爬
取产生影响。有一个十分有用的工具可以检查网站
构建的技术类型——builtwith模块。该模块的安
装方法如下。 pip install builtwith
该模块将URL作为参数,下载该URL并对其进
行分析,然后返回该网站使用的技术。下面是使用
该模块的一个例子。
>>> import builtwith
>>> builtwith.parse('http:example.webscraping.com')
{u'javascript-frameworks': [u'jQuery', u'Modernizr', u'jQuery UI'],u'programming-languages': [u'Python'],u'web-frameworks': [u'Web2py', u'Twitter Bootstrap'],u'web-servers': [u'Nginx']}
从上面的返回结果中可以看出,示例网站使用
了Python的Web2py框架,另外还使用了一些通用的
JavaScript库,因此该网站的内容很有可能是嵌入在
HTML中的,相对而言比较容易抓取。而如果改用
AngularJS构建该网站,此时的网站内容就很可能是
动态加载的。另外,如果网站使用了ASP.NET,那
么在爬取网页时,就必须要用到会话管理和表单提
交了。对于这些更加复杂的情况,我们会在第5章
和第6章中进行介绍。
1.3.5 寻找网站所有者
对于一些网站,我们可能会关心其所有者是
谁。比如,我们已知网站的所有者会封禁网络爬
虫,那么我们最好把下载速度控制得更加保守一些。为了找到网站的所有者,我们可以使用WHOIS
协议查询域名的注册者是谁。Python中有一个针对
该协议的封装库,其文档地址
为https:pypi.python.orgpypipython-whois,我们可以通过pip进行安装。
pip install python-whois
下面是使用该模块对appspot.com这个域名进
行WHOIS查询时的返回结果。
>>> import whois
>>> print whois.whois('appspot.com')
{...
name_servers: [
NS1.GOOGLE.COM,NS2.GOOGLE.COM,NS3.GOOGLE.COM,NS4.GOOGLE.COM,ns4.google.com,ns2.google.com,ns1.google.com,ns3.google.com
],org: Google Inc.,emails: [
abusecomplaints@markmonitor.com,dns-admin@google.com
]
}
从结果中可以看出该域名归属于Google,实际
上也确实如此。该域名是用于Google App Engine服务的。当我们爬取该域名时就需要十分小心,因为
Google经常会阻断网络爬虫,尽管实际上其自身就
是一个网络爬虫业务。1.4 编写第一个网络爬虫
为了抓取网站,我们首先需要下载包含有感兴
趣数据的网页,该过程一般被称为爬取
(crawling)。爬取一个网站有很多种方法,而选
用哪种方法更加合适,则取决于目标网站的结构。
本章中,首先会探讨如何安全地下载网页,然后会
介绍如下3种爬取网站的常见方法:
爬取网站地图;
遍历每个网页的数据库ID;
跟踪网页链接。
1.4.1 下载网页
要想爬取网页,我们首先需要将其下载下来。
下面的示例脚本使用Python的urllib2模块下载
URL。
import urllib2
def download(url):
return urllib2.urlopen(url).read
当传入URL参数时,该函数将会下载网页并返
回其HTML。不过,这个代码片段存在一个问题,即当下载网页时,我们可能会遇到一些无法控制的
错误,比如请求的页面可能不存在。此
时,urllib2会抛出异常,然后退出脚本。安全起
见,下面再给出一个更健壮的版本,可以捕获这些
异常。
import urllib2
def download(url):
print 'Downloading:', url
try:
html = urllib2.urlopen(url).read
except urllib2.URLError as e:
print 'Download error:', e.reason
html = None
return html
现在,当出现下载错误时,该函数能够捕获到
异常,然后返回None。
1.重试下载
下载时遇到的错误经常是临时性的,比如服务
器过载时返回的503 Service Unavailable错误。
对于此类错误,我们可以尝试重新下载,因为这个
服务器问题现在可能已解决。不过,我们不需要对
所有错误都尝试重新下载。如果服务器返回的
是404 Not Found这种错误,则说明该网页目前并
不存在,再次尝试同样的请求一般也不会出现不同
的结果。互联网工程任务组(Internet Engineering Task
Force)定义了HTTP错误的完整列表,详情可参
考https:tools.ietf.orghtmlrfc7231section-
6。从该文档中,我们可以了解到4xx错误发生在请求存在问
题时,而5xx错误则发生在服务端存在问题时。所以,我们只
需要确保download函数在发生5xx`错误时重试下载即
可。下面是支持重试下载功能的新版本 代码。
def download(url, num_retries=2):
print 'Downloading:', url
try:
html = urllib2.urlopen(url).read
except urllib2.URLError as e:
print 'Download error:', e.reason
html = None
if num_retries > 0:
if hasattr(e, 'code') and 500 <= e.code < 600:
recursively retry 5xx HTTP errors
return download(url, num_retries-1)
return html
现在,当download函数遇到5xx错误码时,将
会递归调用函数自身进行重试。此外,该函数还增
加了一个参数,用于设定重试下载的次数,其默认
值为两次。我们在这里限制网页下载的尝试次数,是因为服务器错误可能暂时还没有解决。想要测试
该函数,可以尝试下载http:httpstat.us500,该网址会始终返回500错误码。
>>> download('http:httpstat.us500')
Downloading: http:httpstat.us500
Download error: Internal Server ErrorDownloading: http:httpstat.us500
Download error: Internal Server Error
Downloading: http:httpstat.us500
Download error: Internal Server Error
从上面的返回结果可以看出,download函数的
行为和预期一致,先尝试下载网页,在接收到500
错误后,又进行了两次重试才放弃。
2.设置用户代理
默认情况下,urllib2使用Python-urllib2.7
作为用户代理下载网页内容,其中2.7是Python的
版本号。如果能使用可辨识的用户代理则更好,这
样可以避免我们的网络爬虫碰到一些问题。此外,也许是因为曾经历过质量不佳的Python网络爬虫造
成的服务器过载,一些网站还会封禁这个默认的用
户代理。比如,在使用Python默认用户代理的情况
下,访问http:www.meetup.com``,目前会返回
如图1.3所示的访问拒绝提示。图1.3
因此,为了下载更加可靠,我们需要控制用户
代理的设定。下面的代码对download函数进行了修
改,设定了一个默认的用户代理“wswp”(即Web
Scraping with Python的首字母缩写)。
def download(url, user_agent='wswp', num_retries=2):
print 'Downloading:', url
headers = {'User-agent': user_agent}
request = urllib2.Request(url, headers=headers)
try:
html = urllib2.urlopen(request).read
except urllib2.URLError as e:
print 'Download error:', e.reason
html = None
if num_retries > 0:
if hasattr(e, 'code') and 500 <= e.code < 600:
retry 5XX HTTP errors
return download(url, user_agent, num_retries-1)
return html
现在,我们拥有了一个灵活的下载函数,可以在后续示例中得到复用。该函数能够捕获异常、重
试下载并设置用户代理。
1.4.2 网站地图爬虫
在第一个简单的爬虫中,我们将使用示例网站
robots.txt文件中发现的网站地图来下载所有网
页。为了解析网站地图,我们将会使用一个简单的
正则表达式,从标签中提取出URL。
而在下一章中,我们将会介绍一种更加健壮的解析
方法——CSS选择器。下面是该示例爬虫的代码。
def crawl_sitemap(url):
download the sitemap file
sitemap = download(url)
extract the sitemap links
links = re.findall('(.?)', sitemap)
download each link
for link in links:
html = download(link)
scrape html here
...
现在,运行网站地图爬虫,从示例网站中下载
所有国家页面。
>>> crawl_sitemap('http:example.webscraping.comsitemap.xml')
Downloading: http:example.webscraping.comsitemap.xml
Downloading: http:example.webscraping.comviewAfghanistan-1
Downloading: http:example.webscraping.comviewAland-Islands-2
Downloading: http:example.webscraping.comviewAlbania-3...可以看出,上述运行结果和我们的预期一致,不过正如前文所述,我们无法依靠Sitemap文件提
供每个网页的链接。下一节中,我们将会介绍另一
个简单的爬虫,该爬虫不再依赖于Sitemap文件。
1.4.3 ID遍历爬虫
本节中,我们将利用网站结构的弱点,更加轻
松地访问所有内容。下面是一些示例国家的URL。
http:example.webscraping.comviewAfghanistan-
1
http:example.webscraping.comviewAustralia-
2
http:example.webscraping.comviewBrazil-
3
可以看出,这些URL只在结尾处有所区别,包
括国家名(作为页面别名)和ID。在URL中包含页
面别名是非常普遍的做法,可以对搜索引擎优化起
到帮助作用。一般情况下,Web服务器会忽略这个
字符串,只使用ID来匹配数据库中的相关记录。下
面我们将其移除,加载http:example.``
``webscraping.comview1,测试示例网站中的链
接是否仍然可用。测试结果如图1.4所示。图1.4
从图1.4中可以看出,网页依然可以加载成功,也就是说该方法是有用的。现在,我们就可以忽略页面别名,只遍历ID来下载所有国家的页面。下面
是使用了该技巧的代码片段。
import itertools
for page in itertools.count(1):
url = 'http:example.webscraping.comview-%d' % page
html = download(url)
if html is None:
break
else:
success - can scrape the result
pass
在这段代码中,我们对ID进行遍历,直到出现
下载错误时停止,我们假设此时已到达最后一个国
家的页面。不过,这种实现方式存在一个缺陷,那
就是某些记录可能已被删除,数据库ID之间并不是
连续的。此时,只要访问到某个间隔点,爬虫就会
立即退出。下面是这段代码的改进版本,在该版本
中连续发生多次下载错误后才会退出程序。
maximum number of consecutive download errors allowed
max_errors = 5
current number of consecutive download errors
num_errors = 0
for page in itertools.count(1):
url = 'http:example.webscraping.comview-%d' % page
html = download(url)
if html is None:
received an error trying to download this webpage
num_errors += 1
if num_errors == max_errors:
reached maximum number of
consecutive errors so exit
break
else: success - can scrape the result
...
num_errors = 0
上面代码中实现的爬虫需要连续5次下载错误
才会停止遍历,这样就很大程度上降低了遇到被删
除记录时过早停止遍历的风险。
在爬取网站时,遍历ID是一个很便捷的方法,但是和网站地图爬虫一样,这种方法也无法保证始
终可用。比如,一些网站会检查页面别名是否满足
预期,如果不是,则会返回404 Not Found错误。
而另一些网站则会使用非连续大数作为ID,或是不
使用数值作为ID,此时遍历就难以发挥其作用了。
例如,Amazon使用ISBN作为图书ID,这种编码包
含至少10位数字。使用ID对Amazon的图书进行遍
历需要测试数十亿次,因此这种方法肯定不是抓取
该站内容最高效的方法。
1.4.4 链接爬虫
到目前为止,我们已经利用示例网站的结构特
点实现了两个简单爬虫,用于下载所有的国家页
面。只要这两种技术可用,就应当使用其进行爬
取,因为这两种方法最小化了需要下载的网页数
量。不过,对于另一些网站,我们需要让爬虫表现
得更像普通用户,跟踪链接,访问感兴趣的内容。通过跟踪所有链接的方式,我们可以很容易地
下载整个网站的页面。但是,这种方法会下载大量
我们并不需要的网页。例如,我们想要从一个在线
论坛中抓取用户账号详情页,那么此时我们只需要
下载账号页,而不需要下载讨论贴的页面。本节中
的链接爬虫将使用正则表达式来确定需要下载哪些
页面。下面是这段代码的初始版本。
import re
def link_crawler(seed_url, link_regex):
Crawl from the given seed URL following links matched by link_regex
crawl_queue = [seed_url]
while crawl_queue:
url = crawl_queue.pop
html = download(url)
filter for links matching our regular expression
for link in get_links(html):
if re.match(link_regex, link):
crawl_queue.append(link)
def get_links(html):
Return a list of links from html
a regular expression to extract all links from the webpage
webpage_regex = re.compile(']+href=[\'](.?)[\']',re.IGNORECASE)
list of all links from the webpage
return webpage_regex.findall(html)
要运行这段代码,只需要调用link_crawler函
数,并传入两个参数:要爬取的网站URL和用于跟
踪链接的正则表达式。对于示例网站,我们想要爬
取的是国家列表索引页和国家页面。其中,索引页链接格式如下。
http:example.webscraping.comindex1
http:example.webscraping.comindex2
国家页链接格式如下。
http:example.webscraping.comviewAfghanistan-
1
http:example.webscraping.comviewAland-
Islands-2
因此,我们可以用(index|view)这个简单的
正则表达式来匹配这两类网页。当爬虫使用这些输
入参数运行时会发生什么呢?你会发现我们得到了
如下的下载错误。
>>> link_crawler('http:example.webscraping.com','(index|view)')
Downloading: http:example.webscraping.com
Downloading: index1
Traceback (most recent call last):...
ValueError: unknown url type: index1
可以看出,问题出在下载index1时,该链接
只有网页的路径部分,而没有协议和服务器部分,也就是说这是一个相对链接。由于浏览器知道你正
在浏览哪个网页,所以在浏览器浏览时,相对链接是能够正常工作的。但是,urllib2是无法获知上
下文的。为了让urllib2能够定位网页,我们需要
将链接转换为绝对链接的形式,以便包含定位网页
的所有细节。如你所愿,Python中确实有用来实现
这一功能的模块,该模块称为urlparse。下面
是link_crawler的改进版本,使用了urlparse模块
来创建绝对路径。
import urlparse
def link_crawler(seed_url, link_regex):
Crawl from the given seed URL following links matched by link_regex
crawl_queue = [seed_url]
while crawl_queue:
url = crawl_queue.pop
html = download(url)
for link in get_links(html):
if re.match(link_regex, link):
link = urlparse.urljoin(seed_url, link)
crawl_queue.append(link)
当你运行这段代码时,会发现虽然网页下载没
有出现错误,但是同样的地点总是会被不断下载
到。这是因为这些地点相互之间存在链接。比如,澳大利亚链接到了南极洲,而南极洲也存在到澳大
利亚的链接,此时爬虫就会在它们之间不断循环下
去。要想避免重复爬取相同的链接,我们需要记录
哪些链接已经被爬取过。下面是修改后的
link_crawler函数,已具备存储已发现URL的功
能,可以避免重复下载。 def link_crawler(seed_url, link_regex):
crawl_queue = [seed_url]
keep track which URL's have seen before
seen = set(crawl_queue)
while crawl_queue:
url = crawl_queue.pop
html = download(url)
for link in get_links(html):
check if link matches expected regex
if re.match(link_regex, link):
form absolute link
link = urlparse.urljoin(seed_url, link)
check if have already seen this link
if link not in seen:
seen.add(link)
crawl_queue.append(link)
当运行该脚本时,它会爬取所有地点,并且能
够如期停止。最终,我们得到了一个可用的爬虫!
高级功能
现在,让我们为链接爬虫添加一些功能,使其
在爬取其他网站时更加有用。
解析robots.txt
首先,我们需要解析robots.txt文件,以避免
下载禁止爬取的URL。使用Python自带的
robotparser模块,就可以轻松完成这项工作,如
下面的代码所示。
>>> import robotparser
>>> rp = robotparser.RobotFileParser>>> rp.set_url('http:example.webscraping.comrobots.txt')
>>> rp.read
>>> url = 'http:example.webscraping.com'
>>> user_agent = 'BadCrawler'
>>> rp.can_fetch(user_agent, url)
False
>>> user_agent = 'GoodCrawler'
>>> rp.can_fetch(user_agent, url)
True
robotparser模块首先加载robots.txt文件,然后通过can_fetch函数确定指定的用户代理是
否允许访问网页。在本例中,当用户代理设置为
'BadCrawler' 时,robotparser模块会返回结果表明
无法获取网页,这和示例网站robots.txt``的定义
一样。
为了将该功能集成到爬虫中,我们需要
在crawl循环中添加该检查。...
while crawl_queue:
url = crawl_queue.pop
check url passes robots.txt restrictions
if rp.can_fetch(user_agent, url):...
else:
print 'Blocked by robots.txt:', url
支持代理
有时我们需要使用代理访问某个网站。比如,Netflix屏蔽了美国以外的大多数国家。使用urllib2支持代理并没有想象中那么容易(可以尝试使用更
友好的Python HTTP模块requests来实现该功能,其文档地址为http:docs.python-
requests.org)。下面是使用urllib2支持代理的
代码。
proxy = ...
opener = urllib2.build_opener
proxy_params = {urlparse.urlparse(url).scheme: proxy}
opener.add_handler(urllib2.ProxyHandler(proxy_params))
response = opener.open(request)
下面是集成了该功能的新版本download函数。
def download(url, user_agent='wswp', proxy=None, num_retries=2):
print 'Downloading:', url
headers = {'User-agent': user_agent}
request = urllib2.Request(url, headers=headers)
opener = urllib2.build_opener
if proxy:
proxy_params = {urlparse.urlparse(url).scheme: proxy}
opener.add_handler(urllib2.ProxyHandler(proxy_params))
try:
html = opener.open(request).read
except urllib2.URLError as e:
print 'Download error:', e.reason
html = None
if num_retries > 0:
if hasattr(e, 'code') and 500 <= e.code < 600:
retry 5XX HTTP errors
html = download(url, user_agent, proxy,num_retries-1)
return html
下载限速如果我们爬取网站的速度过快,就会面临被封
禁或是造成服务器过载的风险。为了降低这些风
险,我们可以在两次下载之间添加延时,从而对爬
虫限速。下面是实现了该功能的类的代码。
class Throttle:
Add a delay between downloads to the same domain
def __init__(self, delay):
amount of delay between downloads for each domain
self.delay = delay
timestamp of when a domain was last accessed
self.domains = {}
def wait(self, url):
domain = urlparse.urlparse(url).netloc
last_accessed = self.domains.get(domain)
if self.delay > 0 and last_accessed is not None:
sleep_secs = self.delay - (datetime.datetime.now -
last_accessed).seconds
if sleep_secs > 0:
domain has been accessed recently
so need to sleep
time.sleep(sleep_secs)
update the last accessed time
self.domains[domain] = datetime.datetime.now
Throttle类记录了每个域名上次访问的时间,如果当前时间距离上次访问时间小于指定延时,则
执行睡眠操作。我们可以在每次下载之前调
用Throttle对爬虫进行限速。
throttle = Throttle(delay)...
throttle.wait(url)
result = download(url, headers, proxy=proxy, num_retries=num_retries)
避免爬虫陷阱
目前,我们的爬虫会跟踪所有之前没有访问过
的链接。但是,一些网站会动态生成页面内容,这
样就会出现无限多的网页。比如,网站有一个在线
日历功能,提供了可以访问下个月和下一年的链
接,那么下个月的页面中同样会包含访问再下个月
的链接,这样页面就会无止境地链接下去。这种情
况被称为爬虫陷阱。
想要避免陷入爬虫陷阱,一个简单的方法是记
录到达当前网页经过了多少个链接,也就是深度。
当到达最大深度时,爬虫就不再向队列中添加该网
页中的链接了。要实现这一功能,我们需要修
改seen变量。该变量原先只记录访问过的网页链
接,现在修改为一个字典,增加了页面深度的记
录。
def link_crawler(..., max_depth=2):
max_depth = 2
seen = {}...
depth = seen[url]
if depth != max_depth:
for link in links:
if link not in seen:
seen[link] = depth + 1
crawl_queue.append(link)现在有了这一功能,我们就有信心爬虫最终一
定能够完成。如果想要禁用该功能,只需
将max_depth设为一个负数即可,此时当前深度永
远不会与之相等。
最终版本
这个高级链接爬虫的完整源代码可以
在https:bitbucket.org
wswpcodesrctipchapter01link_crawler3.py
下载得到。要测试这段代码,我们可以将用户代理
设置为BadCrawler,也就是本章前文所述的被
robots.txt屏蔽了的那个用户代理。从下面的运行
结果中可以看出,爬虫果然被屏蔽了,代码启动后
马上就会结束。
>>> seed_url = 'http:example.webscraping.comindex'
>>> link_regex = '(index|view)'
>>> link_crawler(seed_url, link_regex, user_agent='BadCrawler')
Blocked by robots.txt: http:example.webscraping.com
现在,让我们使用默认的用户代理,并将最大
深度设置为1,这样只有主页上的链接才会被下
载。
>>> link_crawler(seed_url, link_regex, max_depth=1)
Downloading: http:example.webscraping.comindex
Downloading: http:example.webscraping.comindex1
Downloading: http:example.webscraping.comviewAntigua-and-Barbuda-10
Downloading: http:example.webscraping.comviewAntarctica-9Downloading: http:example.webscraping.comviewAnguilla-8
Downloading: http:example.webscraping.comviewAngola-7
Downloading: http:example.webscraping.comviewAndorra-6
Downloading: http:example.webscraping.comviewAmerican-Samoa-5
Downloading: http:example.webscraping.comviewAlgeria-4
Downloading: http:example.webscraping.comviewAlbania-3
Downloading: http:example.webscraping.comviewAland-Islands-2
Downloading: http:example.webscraping.comviewAfghanistan-1
和预期一样,爬虫在下载完国家列表的第一页
之后就停止了。1.5 本章小结
本章介绍了网络爬虫,然后开发了一个能够在
后续章节中复用的成熟爬虫。此外,我们还介绍了
一些外部工具和模块的使用方法,用于了解网站、用户代理、网站地图、爬取延时以及各种爬取策
略。
下一章中,我们将讨论如何从已爬取到的网页
中获取数据。第2章 数据抓取
在上一章中,我们构建了一个爬虫,可以通过
跟踪链接的方式下载我们所需的网页。虽然这个例
子很有意思,却不够实用,因为爬虫在下载网页之
后又将结果丢弃掉了。现在,我们需要让这个爬虫
从每个网页中抽取一些数据,然后实现某些事情,这种做法也被称为抓取(scraping)。
首先,我们会介绍一个叫做Firebug Lite的浏
览器扩展,用于检查网页内容,如果你有一些网络
开发背景的话,可能已经对该扩展十分熟悉了。然
后,我们会介绍三种抽取网页数据的方法,分别是
正则表达式、Beautiful Soup和lxml。最后,我们将
对比这三种数据抓取方法。2.1 分析网页
想要了解一个网页的结构如何,可以使用查看
源代码的方法。在大多数浏览器中,都可以在页面
上右键单击选择View page source选项,获取网页
的源代码,如图2.1所示。
我们可以在HTML的下述代码中找到我们感兴
趣的数据。
ISBN:978-7-115-43179-0
本书由人民邮电出版社发行数字版。版权所
有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使
用,未经授权,不得以任何方式复制和传播本书内
容。
我们愿意相信读者具有这样的良知和觉悟,与
我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实
施包括但不限于关闭该帐号等维权措施,并可能追
究法律责任。
著 [澳] Richard Lawson
译 李 斌 责任编辑 傅道坤
人民邮电出版社出版发行 北京市丰台
区成寿寺路11号
邮编 100164 电子邮件
315@ptpress.com.cn
网址 http:www.ptpress.com.cn
读者服务热线:(010)81055410
反盗版热线:(010)81055315版权声明
Copyright ? 2015 Packt Publishing. First
published in the English language under the title Web
Scraping with Python.
All Rights Reserved.
本书由英国Packt Publishing公司授权人民邮电
出版社出版。未经出版者书面许可,对本书的任何
部分不得以任何方式或任何手段复制和传播。
版权所有,侵权必究。内容提要
本书讲解了如何使用Python来编写网络爬虫程
序,内容包括网络爬虫简介,从页面中抓取数据的
三种方法,提取缓存中的数据,使用多个线程和进
程来进行并发抓取,如何抓取动态页面中的内容,与表单进行交互,处理页面中的验证码问题,以及
使用Scarpy和Portia来进行数据抓取,并在最后使用
本书介绍的数据抓取技术对几个真实的网站进行了
抓取,旨在帮助读者活学活用书中介绍的技术。
本书适合有一定Python编程经验,而且对爬虫
技术感兴趣的读者阅读。关于作者
Richard Lawson来自澳大利亚,毕业于墨尔本
大学计算机科学专业。毕业后,他创办了一家专注
于网络爬虫的公司,为超过50个国家的业务提供远
程工作。他精通于世界语,可以使用汉语和韩语对
话,并且积极投身于开源软件。他目前在牛津大学
攻读研究生学位,并利用业余时间研发自主无人
机。
我要感谢Timothy Baldwin教授将我引入这
个令人兴奋的领域,以及本书编写时在巴黎招
待我的Tharavy Douc。关于审稿人
Martin Burch是一名常驻纽约的数据记者,其
工作是为华尔街日报绘制交互式图表。他在新墨西
哥州立大学获得了新闻学和信息系统专业的学士学
位,然后在纽约城市大学新闻学研究院获得了新闻
学专业硕士学位。
我要感谢我的妻子Lisa鼓励我协助本书的
创作,我的叔叔Michael耐心解答我的编程问
题,以及我的父亲Richard激发了我对新闻学和
写作的热爱。
William Sankey是一位数据专业人士,也是一
位业余开发人员,生活在马里兰州科利奇帕克市。
他于2012年毕业于约翰?霍普金斯大学,获得了公
共政策硕士学位,专业方向为定量分析。他目前在
LM政策研究有限责任公司担任健康服务研究员,从事与美国医疗保险和医疗补助服务中心(CMS)
相关的项目。这些项目包括责任医疗机构评估以及
精神病院住院患者预付费系统监测。我要感谢我深爱的妻子Julia和顽皮的小猫
Ruby,给予我全部的爱和支持。
Ayush Tiwari是一名Python开发者,本科就读
于印度理工学院罗克分校。他自2013年起工作于印
度理工学院罗克分校信息管理小组,并活跃于网络
开发领域。对他而言,审阅本书是一个非常棒的经
历。他不仅是一名审稿人,也是一名狂热的网络爬
虫学习者。他向所有Python爱好者推荐本书,以便
享受爬虫的益处。 他热衷于Python网络爬虫,曾参
与体育直播订阅、通用Python电子商务网络爬虫
(在Miranj)等相关项目。 他还使用Django应用开
发了就业门户,帮助改善印度理工学院罗克分校的
就业流程。 除了后端开发之外,他还喜欢使用诸如
NumPy、SciPy等Python库进行科学计算和数据分
析,目前他从事计算流体力学领域的研究。你可以
在GitHub上访问到他的项目,他的用户名是
tiwariayush。 他喜欢徒步穿越喜马拉雅山谷,每年
会参加多次徒步行走活动。此外,他还喜欢弹吉
他。他的成就还包括参加国际知名的Super 30小
组,并在其中成为排名保持者。他在高中时,还参
加了国际奥林匹克数学竞赛。我的家庭成员(我的姐姐Aditi、我的父母以及
Anand先生)、我在VI和IMG的朋友以及我的教授
都为我提供了很大的帮助。我要感谢他们所有人对
我的支持。
最后,感谢尊敬的作者和Packt出版社团队出版
了这些非常好的技术书籍。我要对他们在编写这些
书籍时的所有辛苦工作表示赞赏。前言
互联网包含了迄今为止最有用的数据集,并且
大部分可以免费公开访问。但是,这些数据难以复
用。它们被嵌入在网站的结构和样式当中,需要抽
取出来才能使用。从网页中抽取数据的过程又被称
为网络爬虫。随着越来越多的信息被发布到网络
上,网络爬虫也变得越来越有用。阅读本书的前提
本书中所有的代码都已经在Python 2.7环境中
进行过测试,并且可以从
http:bitbucket.orgwswpcode下载到这些源代
码。理想情况下,本书未来的版本会将示例代码移
植到Python 3当中。不过,现在依赖的很多库(比
如ScrapyTwisted、Mechanize和Ghost)还只支持
Python 2。为了帮助阐明爬取示例,我们创建了一
个示例网站,其网址为http:example.
webscraping.com。由于该网站限制了下载内容的
速度,因此如果你希望自行搭建示例网站,可以从
http:bitbucket.orgwswpplaces获取网站源代
码和安装说明。
我们决定为本书中使用的大部分示例搭建一个
定制网站,而不是抓取活跃网站,这样我们就对环
境拥有了完全控制。这种方式为我们提供了稳定
性,因为活跃网站要比书中的定制网站更新更加频
繁,并且当你尝试运行爬虫示例时,代码可能已经
无法工作。另外,定制网站允许我们自定义示例,用于阐释特定技巧并避免其他干扰。最后,活跃网
站可能并不欢迎我们使用它作为学习网络爬虫的对
象,并且可能会尝试封禁我们的爬虫。使用我们自己定制的网站可以规避这些风险,不过在这些例子
中学到的技巧确实也可以应用到这些活跃网站当
中。本书读者
阅读本书需要有一定的编程经验,并且不适用
于绝对的初学者。在实践中,我们将会首先实现我
们自己的网络爬虫技术版本,然后才会介绍现有的
流行模块,这样可以让你更好地理解这些技术是如
何工作的。本书中的这些示例将假设你已经拥有
Python语言以及使用pip安装模块的能力。如果你
想复习一下这些知识,有一本非常好的免费在线书
籍可以使用,其作者为Mark Pilgrim,书籍网址
是http:www.diveintopython.net。这本书也是
我初学Python时所使用的资源。
此外,这些例子还假设你已经了解网页是如何
使用HTML进行构建并通过JavaScript更新的知识。
关于HTTP、CSS、AJAX、WebKit以及MongoDB的
既有知识也很有用,不过它们不是必需的,这些技
术会在需要使用时进行介绍。上述很多主题的详细
参考资料可以从http:www.w3schools.com获取
到。第1章 网络爬虫简介
本章中,我们将会介绍如下主题:
网络爬虫领域简介;
解释合法性质疑;
对目标网站进行背景调研;
逐步完善一个高级网络爬虫。1.1 网络爬虫何时有用
假设我有一个鞋店,并且想要及时了解竞争对
手的价格。我可以每天访问他们的网站,与我店铺
中鞋子的价格进行对比。但是,如果我店铺中的鞋
类品种繁多,或是希望能够更加频繁地查看价格变
化的话,就需要花费大量的时间,甚至难以实现。
再举一个例子,我看中了一双鞋,想等它促销时再
购买。我可能需要每天访问这家鞋店的网站来查看
这双鞋是否降价,也许需要等待几个月的时间,我
才能如愿盼到这双鞋促销。上述这 两个重复性的手
工流程,都可以利用本书介绍的网络爬虫技术实现
自动化处理。
理想状态下,网络爬虫并不是必须品,每个网
站都应该提供API,以结构化的格式共享它们的数
据。然而现实情况中,虽然一些网站已经提供了这
种API,但是它们通常会限制可以抓取的数据,以
及访问这些数据的频率。另外,对于网站的开发者
而言,维护前端界面比维护后端API接口优先级更
高。总之,我们不能仅仅依赖于API去访问我们所
需的在线数据,而是应该学习一些网络爬虫技术的
相关知识。1.2 网络爬虫是否合法
网络爬虫目前还处于早期的蛮荒阶段,“允许
哪些行为”这种基本秩序还处于建设之中。从目前
的实践来看,如果抓取数据的行为用于个人使用,则不存在问题;而如果数据用于转载,那么抓取的
数据类型就非常关键了。
世界各地法院的一些案件可以帮助我们确定哪
些网络爬虫行为是允许的。在Feist Publications,Inc.起诉Rural Telephone Service Co.的案件中,美国
联邦最高法院裁定抓取并转载真实数据(比如,电
话清单)是允许的。而在澳大利亚,Telstra
Corporation Limited起诉Phone Directories Company
Pty Ltd这一类似案件中,则裁定只有拥有明确作者
的数据,才可以获得版权。此外,在欧盟的ofir.dk
起诉home.dk一案中,最终裁定定期抓取和深度链
接是允许的。
这些案件告诉我们,当抓取的数据是现实生活
中的真实数据(比如,营业地址、电话清单)时,是允许转载的。但是,如果是原创数据(比如,意
见和评论),通常就会受到版权限制,而不能转
载。无论如何,当你抓取某个网站的数据时,请记
住自己是该网站的访客,应当约束自己的抓取行
为,否则他们可能会封禁你的IP,甚至采取更进一
步的法律行动。这就要求下载请求的速度需要限定
在一个合理值之内,并且还需要设定一个专属的用
户代理来标识自己。在下面的小节中我们将会对这
些实践进行具体介绍。
关于上述几个法律案件的更多信息可以参考下述地址:
http:caselaw.lp.findlaw.comscriptsgetcase. pl?
court=USvol=499invol=340
http:www.austlii.edu.auaucasescthFCA201044.html
http:www.bvhd.dkuploadstx_mocarticlesS_og_Handelsrettens_afg_relse_i_Ofir-
sagen.pdf1.3 背景调研
在深入讨论爬取一个网站之前,我们首先需要
对目标站点的规模和结构进行一定程度的了解。网
站自身的robots.txt和Sitemap文件都可以为我们
提供一定的帮助,此外还有一些能提供更详细信息
的外部工具,比如Google搜索和WHOIS。
1.3.1 检查robots.txt
大多数网站都会定义robots.txt文件,这样可
以让爬虫了解爬取该网站时存在哪些限制。这些限
制虽然仅仅作为建议给出,但是良好的网络公民都
应当遵守这些限制。在爬取之前,检查robots.txt
文件这一宝贵资源可以最小化爬虫被封禁的可能,而且还能发现和网站结构相关的线索。关于
robots.txt协议的更多信息可以参见
http:www.robotstxt.org。下面的代码是我们的
示例文件robots.txt中的内容,可以访问
http:example.webscraping.comrobots.txt获
取。
section 1
User-agent: BadCrawler
Disallow: section 2
User-agent:
Crawl-delay: 5
Disallow: trap
section 3
Sitemap: http:example.webscraping.comsitemap.xml
在section 1中,robots.txt文件禁止用户代
理为BadCrawler的爬虫爬取该网站,不过这种写法
可能无法起到应有的作用,因为恶意爬虫根本不会
遵从robots.txt的要求。本章后面的一个例子将会
展示如何让爬虫自动遵守robots.txt的要求。
section 2规定,无论使用哪种用户代理,都
应该在两次下载请求之间给出5秒的抓取延迟,我
们需要遵从该建议以避免服务器过载。这里还有一
个trap链接,用于封禁那些爬取了不允许链接的
恶意爬虫。如果你访问了这个链接,服务器就会封
禁你的IP一分钟!一个真实的网站可能会对你的IP
封禁更长时间,甚至是永久封禁。不过如果这样设
置的话,我们就无法继续这个例子了。
section 3定义了一个Sitemap文件,我们将在
下一节中了解如何检查该文件。
1.3.2 检查网站地图
网站提供的Sitemap文件(即网站地图)可以帮助爬虫定位网站最新的内容,而无须爬取每一个
网页。如果想要了解更多信息,可以从
http:www.sitemaps.orgprotocol.html获取网站
地图标准的定义。下面是在robots.txt文件中发现
的Sitemap文件的内容。
网站地图提供了所有网页的链接,我们会在后
面的小节中使用这些信息,用于创建我们的第一个
爬虫。虽然Sitemap文件提供了一种爬取网站的有
效方式,但是我们仍需对其谨慎处理,因为该文件
经常存在缺失、过期或不完整的问题。
1.3.3 估算网站大小
目标网站的大小会影响我们如何进行爬取。如
果是像我们的示例站点这样只有几百个URL的网
站,效率并没有那么重要;但如果是拥有数百万个
网页的站点,使用串行下载可能需要持续数月才能
完成,这时就需要使用第4章中介绍的分布式下载来解决了。
估算网站大小的一个简便方法是检查Google爬
虫的结果,因为Google很可能已经爬取过我们感兴
趣的网站。我们可以通过Google搜索的site关键词
过滤域名结果,从而获取该信息。我们可以从
http:www.google.comadvanced_search了解到该
接口及其他高级搜索参数的用法。
图1.1所示为使用site关键词对我们的示例网站进行
搜索的结果,即在Google中搜
索site:example.webscraping.com。
从图1.1中可以看出,此时Google估算该网站拥
有202个网页,这和实际情况差不多。不过对于更
大型的网站,我们会发现Google的估算并不十分准
确。
在域名后面添加URL路径,可以对结果进行过
滤,仅显示网站的某些部分。图1.2所示为搜
索site:example.webscraping.comview的结果。该
搜索条件会限制Google只搜索国家页面。图1.1图1.2
这种附加的过滤条件非常有用,因为在理想情
况下,你只希望爬取网站中包含有用数据的部分,而不是爬取网站的每个页面。
1.3.4 识别网站所用技术
构建网站所使用的技术类型也会对我们如何爬
取产生影响。有一个十分有用的工具可以检查网站
构建的技术类型——builtwith模块。该模块的安
装方法如下。 pip install builtwith
该模块将URL作为参数,下载该URL并对其进
行分析,然后返回该网站使用的技术。下面是使用
该模块的一个例子。
>>> import builtwith
>>> builtwith.parse('http:example.webscraping.com')
{u'javascript-frameworks': [u'jQuery', u'Modernizr', u'jQuery UI'],u'programming-languages': [u'Python'],u'web-frameworks': [u'Web2py', u'Twitter Bootstrap'],u'web-servers': [u'Nginx']}
从上面的返回结果中可以看出,示例网站使用
了Python的Web2py框架,另外还使用了一些通用的
JavaScript库,因此该网站的内容很有可能是嵌入在
HTML中的,相对而言比较容易抓取。而如果改用
AngularJS构建该网站,此时的网站内容就很可能是
动态加载的。另外,如果网站使用了ASP.NET,那
么在爬取网页时,就必须要用到会话管理和表单提
交了。对于这些更加复杂的情况,我们会在第5章
和第6章中进行介绍。
1.3.5 寻找网站所有者
对于一些网站,我们可能会关心其所有者是
谁。比如,我们已知网站的所有者会封禁网络爬
虫,那么我们最好把下载速度控制得更加保守一些。为了找到网站的所有者,我们可以使用WHOIS
协议查询域名的注册者是谁。Python中有一个针对
该协议的封装库,其文档地址
为https:pypi.python.orgpypipython-whois,我们可以通过pip进行安装。
pip install python-whois
下面是使用该模块对appspot.com这个域名进
行WHOIS查询时的返回结果。
>>> import whois
>>> print whois.whois('appspot.com')
{...
name_servers: [
NS1.GOOGLE.COM,NS2.GOOGLE.COM,NS3.GOOGLE.COM,NS4.GOOGLE.COM,ns4.google.com,ns2.google.com,ns1.google.com,ns3.google.com
],org: Google Inc.,emails: [
abusecomplaints@markmonitor.com,dns-admin@google.com
]
}
从结果中可以看出该域名归属于Google,实际
上也确实如此。该域名是用于Google App Engine服务的。当我们爬取该域名时就需要十分小心,因为
Google经常会阻断网络爬虫,尽管实际上其自身就
是一个网络爬虫业务。1.4 编写第一个网络爬虫
为了抓取网站,我们首先需要下载包含有感兴
趣数据的网页,该过程一般被称为爬取
(crawling)。爬取一个网站有很多种方法,而选
用哪种方法更加合适,则取决于目标网站的结构。
本章中,首先会探讨如何安全地下载网页,然后会
介绍如下3种爬取网站的常见方法:
爬取网站地图;
遍历每个网页的数据库ID;
跟踪网页链接。
1.4.1 下载网页
要想爬取网页,我们首先需要将其下载下来。
下面的示例脚本使用Python的urllib2模块下载
URL。
import urllib2
def download(url):
return urllib2.urlopen(url).read
当传入URL参数时,该函数将会下载网页并返
回其HTML。不过,这个代码片段存在一个问题,即当下载网页时,我们可能会遇到一些无法控制的
错误,比如请求的页面可能不存在。此
时,urllib2会抛出异常,然后退出脚本。安全起
见,下面再给出一个更健壮的版本,可以捕获这些
异常。
import urllib2
def download(url):
print 'Downloading:', url
try:
html = urllib2.urlopen(url).read
except urllib2.URLError as e:
print 'Download error:', e.reason
html = None
return html
现在,当出现下载错误时,该函数能够捕获到
异常,然后返回None。
1.重试下载
下载时遇到的错误经常是临时性的,比如服务
器过载时返回的503 Service Unavailable错误。
对于此类错误,我们可以尝试重新下载,因为这个
服务器问题现在可能已解决。不过,我们不需要对
所有错误都尝试重新下载。如果服务器返回的
是404 Not Found这种错误,则说明该网页目前并
不存在,再次尝试同样的请求一般也不会出现不同
的结果。互联网工程任务组(Internet Engineering Task
Force)定义了HTTP错误的完整列表,详情可参
考https:tools.ietf.orghtmlrfc7231section-
6。从该文档中,我们可以了解到4xx错误发生在请求存在问
题时,而5xx错误则发生在服务端存在问题时。所以,我们只
需要确保download函数在发生5xx`错误时重试下载即
可。下面是支持重试下载功能的新版本 代码。
def download(url, num_retries=2):
print 'Downloading:', url
try:
html = urllib2.urlopen(url).read
except urllib2.URLError as e:
print 'Download error:', e.reason
html = None
if num_retries > 0:
if hasattr(e, 'code') and 500 <= e.code < 600:
recursively retry 5xx HTTP errors
return download(url, num_retries-1)
return html
现在,当download函数遇到5xx错误码时,将
会递归调用函数自身进行重试。此外,该函数还增
加了一个参数,用于设定重试下载的次数,其默认
值为两次。我们在这里限制网页下载的尝试次数,是因为服务器错误可能暂时还没有解决。想要测试
该函数,可以尝试下载http:httpstat.us500,该网址会始终返回500错误码。
>>> download('http:httpstat.us500')
Downloading: http:httpstat.us500
Download error: Internal Server ErrorDownloading: http:httpstat.us500
Download error: Internal Server Error
Downloading: http:httpstat.us500
Download error: Internal Server Error
从上面的返回结果可以看出,download函数的
行为和预期一致,先尝试下载网页,在接收到500
错误后,又进行了两次重试才放弃。
2.设置用户代理
默认情况下,urllib2使用Python-urllib2.7
作为用户代理下载网页内容,其中2.7是Python的
版本号。如果能使用可辨识的用户代理则更好,这
样可以避免我们的网络爬虫碰到一些问题。此外,也许是因为曾经历过质量不佳的Python网络爬虫造
成的服务器过载,一些网站还会封禁这个默认的用
户代理。比如,在使用Python默认用户代理的情况
下,访问http:www.meetup.com``,目前会返回
如图1.3所示的访问拒绝提示。图1.3
因此,为了下载更加可靠,我们需要控制用户
代理的设定。下面的代码对download函数进行了修
改,设定了一个默认的用户代理“wswp”(即Web
Scraping with Python的首字母缩写)。
def download(url, user_agent='wswp', num_retries=2):
print 'Downloading:', url
headers = {'User-agent': user_agent}
request = urllib2.Request(url, headers=headers)
try:
html = urllib2.urlopen(request).read
except urllib2.URLError as e:
print 'Download error:', e.reason
html = None
if num_retries > 0:
if hasattr(e, 'code') and 500 <= e.code < 600:
retry 5XX HTTP errors
return download(url, user_agent, num_retries-1)
return html
现在,我们拥有了一个灵活的下载函数,可以在后续示例中得到复用。该函数能够捕获异常、重
试下载并设置用户代理。
1.4.2 网站地图爬虫
在第一个简单的爬虫中,我们将使用示例网站
robots.txt文件中发现的网站地图来下载所有网
页。为了解析网站地图,我们将会使用一个简单的
正则表达式,从
而在下一章中,我们将会介绍一种更加健壮的解析
方法——CSS选择器。下面是该示例爬虫的代码。
def crawl_sitemap(url):
download the sitemap file
sitemap = download(url)
extract the sitemap links
links = re.findall('
download each link
for link in links:
html = download(link)
scrape html here
...
现在,运行网站地图爬虫,从示例网站中下载
所有国家页面。
>>> crawl_sitemap('http:example.webscraping.comsitemap.xml')
Downloading: http:example.webscraping.comsitemap.xml
Downloading: http:example.webscraping.comviewAfghanistan-1
Downloading: http:example.webscraping.comviewAland-Islands-2
Downloading: http:example.webscraping.comviewAlbania-3...可以看出,上述运行结果和我们的预期一致,不过正如前文所述,我们无法依靠Sitemap文件提
供每个网页的链接。下一节中,我们将会介绍另一
个简单的爬虫,该爬虫不再依赖于Sitemap文件。
1.4.3 ID遍历爬虫
本节中,我们将利用网站结构的弱点,更加轻
松地访问所有内容。下面是一些示例国家的URL。
http:example.webscraping.comviewAfghanistan-
1
http:example.webscraping.comviewAustralia-
2
http:example.webscraping.comviewBrazil-
3
可以看出,这些URL只在结尾处有所区别,包
括国家名(作为页面别名)和ID。在URL中包含页
面别名是非常普遍的做法,可以对搜索引擎优化起
到帮助作用。一般情况下,Web服务器会忽略这个
字符串,只使用ID来匹配数据库中的相关记录。下
面我们将其移除,加载http:example.``
``webscraping.comview1,测试示例网站中的链
接是否仍然可用。测试结果如图1.4所示。图1.4
从图1.4中可以看出,网页依然可以加载成功,也就是说该方法是有用的。现在,我们就可以忽略页面别名,只遍历ID来下载所有国家的页面。下面
是使用了该技巧的代码片段。
import itertools
for page in itertools.count(1):
url = 'http:example.webscraping.comview-%d' % page
html = download(url)
if html is None:
break
else:
success - can scrape the result
pass
在这段代码中,我们对ID进行遍历,直到出现
下载错误时停止,我们假设此时已到达最后一个国
家的页面。不过,这种实现方式存在一个缺陷,那
就是某些记录可能已被删除,数据库ID之间并不是
连续的。此时,只要访问到某个间隔点,爬虫就会
立即退出。下面是这段代码的改进版本,在该版本
中连续发生多次下载错误后才会退出程序。
maximum number of consecutive download errors allowed
max_errors = 5
current number of consecutive download errors
num_errors = 0
for page in itertools.count(1):
url = 'http:example.webscraping.comview-%d' % page
html = download(url)
if html is None:
received an error trying to download this webpage
num_errors += 1
if num_errors == max_errors:
reached maximum number of
consecutive errors so exit
break
else: success - can scrape the result
...
num_errors = 0
上面代码中实现的爬虫需要连续5次下载错误
才会停止遍历,这样就很大程度上降低了遇到被删
除记录时过早停止遍历的风险。
在爬取网站时,遍历ID是一个很便捷的方法,但是和网站地图爬虫一样,这种方法也无法保证始
终可用。比如,一些网站会检查页面别名是否满足
预期,如果不是,则会返回404 Not Found错误。
而另一些网站则会使用非连续大数作为ID,或是不
使用数值作为ID,此时遍历就难以发挥其作用了。
例如,Amazon使用ISBN作为图书ID,这种编码包
含至少10位数字。使用ID对Amazon的图书进行遍
历需要测试数十亿次,因此这种方法肯定不是抓取
该站内容最高效的方法。
1.4.4 链接爬虫
到目前为止,我们已经利用示例网站的结构特
点实现了两个简单爬虫,用于下载所有的国家页
面。只要这两种技术可用,就应当使用其进行爬
取,因为这两种方法最小化了需要下载的网页数
量。不过,对于另一些网站,我们需要让爬虫表现
得更像普通用户,跟踪链接,访问感兴趣的内容。通过跟踪所有链接的方式,我们可以很容易地
下载整个网站的页面。但是,这种方法会下载大量
我们并不需要的网页。例如,我们想要从一个在线
论坛中抓取用户账号详情页,那么此时我们只需要
下载账号页,而不需要下载讨论贴的页面。本节中
的链接爬虫将使用正则表达式来确定需要下载哪些
页面。下面是这段代码的初始版本。
import re
def link_crawler(seed_url, link_regex):
Crawl from the given seed URL following links matched by link_regex
crawl_queue = [seed_url]
while crawl_queue:
url = crawl_queue.pop
html = download(url)
filter for links matching our regular expression
for link in get_links(html):
if re.match(link_regex, link):
crawl_queue.append(link)
def get_links(html):
Return a list of links from html
a regular expression to extract all links from the webpage
webpage_regex = re.compile(']+href=[\'](.?)[\']',re.IGNORECASE)
list of all links from the webpage
return webpage_regex.findall(html)
要运行这段代码,只需要调用link_crawler函
数,并传入两个参数:要爬取的网站URL和用于跟
踪链接的正则表达式。对于示例网站,我们想要爬
取的是国家列表索引页和国家页面。其中,索引页链接格式如下。
http:example.webscraping.comindex1
http:example.webscraping.comindex2
国家页链接格式如下。
http:example.webscraping.comviewAfghanistan-
1
http:example.webscraping.comviewAland-
Islands-2
因此,我们可以用(index|view)这个简单的
正则表达式来匹配这两类网页。当爬虫使用这些输
入参数运行时会发生什么呢?你会发现我们得到了
如下的下载错误。
>>> link_crawler('http:example.webscraping.com','(index|view)')
Downloading: http:example.webscraping.com
Downloading: index1
Traceback (most recent call last):...
ValueError: unknown url type: index1
可以看出,问题出在下载index1时,该链接
只有网页的路径部分,而没有协议和服务器部分,也就是说这是一个相对链接。由于浏览器知道你正
在浏览哪个网页,所以在浏览器浏览时,相对链接是能够正常工作的。但是,urllib2是无法获知上
下文的。为了让urllib2能够定位网页,我们需要
将链接转换为绝对链接的形式,以便包含定位网页
的所有细节。如你所愿,Python中确实有用来实现
这一功能的模块,该模块称为urlparse。下面
是link_crawler的改进版本,使用了urlparse模块
来创建绝对路径。
import urlparse
def link_crawler(seed_url, link_regex):
Crawl from the given seed URL following links matched by link_regex
crawl_queue = [seed_url]
while crawl_queue:
url = crawl_queue.pop
html = download(url)
for link in get_links(html):
if re.match(link_regex, link):
link = urlparse.urljoin(seed_url, link)
crawl_queue.append(link)
当你运行这段代码时,会发现虽然网页下载没
有出现错误,但是同样的地点总是会被不断下载
到。这是因为这些地点相互之间存在链接。比如,澳大利亚链接到了南极洲,而南极洲也存在到澳大
利亚的链接,此时爬虫就会在它们之间不断循环下
去。要想避免重复爬取相同的链接,我们需要记录
哪些链接已经被爬取过。下面是修改后的
link_crawler函数,已具备存储已发现URL的功
能,可以避免重复下载。 def link_crawler(seed_url, link_regex):
crawl_queue = [seed_url]
keep track which URL's have seen before
seen = set(crawl_queue)
while crawl_queue:
url = crawl_queue.pop
html = download(url)
for link in get_links(html):
check if link matches expected regex
if re.match(link_regex, link):
form absolute link
link = urlparse.urljoin(seed_url, link)
check if have already seen this link
if link not in seen:
seen.add(link)
crawl_queue.append(link)
当运行该脚本时,它会爬取所有地点,并且能
够如期停止。最终,我们得到了一个可用的爬虫!
高级功能
现在,让我们为链接爬虫添加一些功能,使其
在爬取其他网站时更加有用。
解析robots.txt
首先,我们需要解析robots.txt文件,以避免
下载禁止爬取的URL。使用Python自带的
robotparser模块,就可以轻松完成这项工作,如
下面的代码所示。
>>> import robotparser
>>> rp = robotparser.RobotFileParser>>> rp.set_url('http:example.webscraping.comrobots.txt')
>>> rp.read
>>> url = 'http:example.webscraping.com'
>>> user_agent = 'BadCrawler'
>>> rp.can_fetch(user_agent, url)
False
>>> user_agent = 'GoodCrawler'
>>> rp.can_fetch(user_agent, url)
True
robotparser模块首先加载robots.txt文件,然后通过can_fetch函数确定指定的用户代理是
否允许访问网页。在本例中,当用户代理设置为
'BadCrawler' 时,robotparser模块会返回结果表明
无法获取网页,这和示例网站robots.txt``的定义
一样。
为了将该功能集成到爬虫中,我们需要
在crawl循环中添加该检查。...
while crawl_queue:
url = crawl_queue.pop
check url passes robots.txt restrictions
if rp.can_fetch(user_agent, url):...
else:
print 'Blocked by robots.txt:', url
支持代理
有时我们需要使用代理访问某个网站。比如,Netflix屏蔽了美国以外的大多数国家。使用urllib2支持代理并没有想象中那么容易(可以尝试使用更
友好的Python HTTP模块requests来实现该功能,其文档地址为http:docs.python-
requests.org)。下面是使用urllib2支持代理的
代码。
proxy = ...
opener = urllib2.build_opener
proxy_params = {urlparse.urlparse(url).scheme: proxy}
opener.add_handler(urllib2.ProxyHandler(proxy_params))
response = opener.open(request)
下面是集成了该功能的新版本download函数。
def download(url, user_agent='wswp', proxy=None, num_retries=2):
print 'Downloading:', url
headers = {'User-agent': user_agent}
request = urllib2.Request(url, headers=headers)
opener = urllib2.build_opener
if proxy:
proxy_params = {urlparse.urlparse(url).scheme: proxy}
opener.add_handler(urllib2.ProxyHandler(proxy_params))
try:
html = opener.open(request).read
except urllib2.URLError as e:
print 'Download error:', e.reason
html = None
if num_retries > 0:
if hasattr(e, 'code') and 500 <= e.code < 600:
retry 5XX HTTP errors
html = download(url, user_agent, proxy,num_retries-1)
return html
下载限速如果我们爬取网站的速度过快,就会面临被封
禁或是造成服务器过载的风险。为了降低这些风
险,我们可以在两次下载之间添加延时,从而对爬
虫限速。下面是实现了该功能的类的代码。
class Throttle:
Add a delay between downloads to the same domain
def __init__(self, delay):
amount of delay between downloads for each domain
self.delay = delay
timestamp of when a domain was last accessed
self.domains = {}
def wait(self, url):
domain = urlparse.urlparse(url).netloc
last_accessed = self.domains.get(domain)
if self.delay > 0 and last_accessed is not None:
sleep_secs = self.delay - (datetime.datetime.now -
last_accessed).seconds
if sleep_secs > 0:
domain has been accessed recently
so need to sleep
time.sleep(sleep_secs)
update the last accessed time
self.domains[domain] = datetime.datetime.now
Throttle类记录了每个域名上次访问的时间,如果当前时间距离上次访问时间小于指定延时,则
执行睡眠操作。我们可以在每次下载之前调
用Throttle对爬虫进行限速。
throttle = Throttle(delay)...
throttle.wait(url)
result = download(url, headers, proxy=proxy, num_retries=num_retries)
避免爬虫陷阱
目前,我们的爬虫会跟踪所有之前没有访问过
的链接。但是,一些网站会动态生成页面内容,这
样就会出现无限多的网页。比如,网站有一个在线
日历功能,提供了可以访问下个月和下一年的链
接,那么下个月的页面中同样会包含访问再下个月
的链接,这样页面就会无止境地链接下去。这种情
况被称为爬虫陷阱。
想要避免陷入爬虫陷阱,一个简单的方法是记
录到达当前网页经过了多少个链接,也就是深度。
当到达最大深度时,爬虫就不再向队列中添加该网
页中的链接了。要实现这一功能,我们需要修
改seen变量。该变量原先只记录访问过的网页链
接,现在修改为一个字典,增加了页面深度的记
录。
def link_crawler(..., max_depth=2):
max_depth = 2
seen = {}...
depth = seen[url]
if depth != max_depth:
for link in links:
if link not in seen:
seen[link] = depth + 1
crawl_queue.append(link)现在有了这一功能,我们就有信心爬虫最终一
定能够完成。如果想要禁用该功能,只需
将max_depth设为一个负数即可,此时当前深度永
远不会与之相等。
最终版本
这个高级链接爬虫的完整源代码可以
在https:bitbucket.org
wswpcodesrctipchapter01link_crawler3.py
下载得到。要测试这段代码,我们可以将用户代理
设置为BadCrawler,也就是本章前文所述的被
robots.txt屏蔽了的那个用户代理。从下面的运行
结果中可以看出,爬虫果然被屏蔽了,代码启动后
马上就会结束。
>>> seed_url = 'http:example.webscraping.comindex'
>>> link_regex = '(index|view)'
>>> link_crawler(seed_url, link_regex, user_agent='BadCrawler')
Blocked by robots.txt: http:example.webscraping.com
现在,让我们使用默认的用户代理,并将最大
深度设置为1,这样只有主页上的链接才会被下
载。
>>> link_crawler(seed_url, link_regex, max_depth=1)
Downloading: http:example.webscraping.comindex
Downloading: http:example.webscraping.comindex1
Downloading: http:example.webscraping.comviewAntigua-and-Barbuda-10
Downloading: http:example.webscraping.comviewAntarctica-9Downloading: http:example.webscraping.comviewAnguilla-8
Downloading: http:example.webscraping.comviewAngola-7
Downloading: http:example.webscraping.comviewAndorra-6
Downloading: http:example.webscraping.comviewAmerican-Samoa-5
Downloading: http:example.webscraping.comviewAlgeria-4
Downloading: http:example.webscraping.comviewAlbania-3
Downloading: http:example.webscraping.comviewAland-Islands-2
Downloading: http:example.webscraping.comviewAfghanistan-1
和预期一样,爬虫在下载完国家列表的第一页
之后就停止了。1.5 本章小结
本章介绍了网络爬虫,然后开发了一个能够在
后续章节中复用的成熟爬虫。此外,我们还介绍了
一些外部工具和模块的使用方法,用于了解网站、用户代理、网站地图、爬取延时以及各种爬取策
略。
下一章中,我们将讨论如何从已爬取到的网页
中获取数据。第2章 数据抓取
在上一章中,我们构建了一个爬虫,可以通过
跟踪链接的方式下载我们所需的网页。虽然这个例
子很有意思,却不够实用,因为爬虫在下载网页之
后又将结果丢弃掉了。现在,我们需要让这个爬虫
从每个网页中抽取一些数据,然后实现某些事情,这种做法也被称为抓取(scraping)。
首先,我们会介绍一个叫做Firebug Lite的浏
览器扩展,用于检查网页内容,如果你有一些网络
开发背景的话,可能已经对该扩展十分熟悉了。然
后,我们会介绍三种抽取网页数据的方法,分别是
正则表达式、Beautiful Soup和lxml。最后,我们将
对比这三种数据抓取方法。2.1 分析网页
想要了解一个网页的结构如何,可以使用查看
源代码的方法。在大多数浏览器中,都可以在页面
上右键单击选择View page source选项,获取网页
的源代码,如图2.1所示。
我们可以在HTML的下述代码中找到我们感兴
趣的数据。
for=places_national_flag id=places_national_flag__label>National Flag: |





