在本文中,咱们将分析几个真实网站,来看看咱们在《用Python写网络爬虫(第2版)》中学过的这些技巧是如何应用的。首先咱们使用Google演示一个真实的搜索表单,而后是依赖JavaScript和API的网站Facebook,接下来是典型的在线商店Gap。因为这些都是活跃的网站,所以读者在阅读本书时这些网站存在已经发生变动的风险。css
[德] 凯瑟琳,雅姆尔 著python
不过这样也好,由于本文示例的目的是为了向你展现如何应用前面所学的技术,而不是展现如何抓取任何网站。当你选择运行某个示例时,首先须要检查网站结构在示例编写后是否发生过改变,以及当前该网站的条款与条件是否禁止了爬虫。
web
在本文中,咱们将介绍以下主题:正则表达式
抓取Google搜索结果网页;api
调研Facebook的API;浏览器
在Gap网站中使用多线程;缓存
“Google搜索引擎”微信
为了了解咱们对CSS选择器知识的使用状况,咱们将会抓取Google的搜索结果。根据中Alexa的数据,Google是全世界最流行的网站之一,并且很是方便的是,该网站结构简单,易于抓取。网络
图1.1所示为Google搜索主页使用浏览器工具加载查看表单元素时的界面。
图1.1
能够看到,搜索查询存储在输入参数q当中,而后表单提交到action属性设定的/search路径。咱们能够经过将test、做为搜索条件提交给表单对其进行测试,此时会跳转到相似https://www.google.ro/?gws_ rd=cr,ssl&ei=TuXYWJXqBsGsswHO8YiQAQ#q=test&*的 URL中。确切的URL取决于你的浏览器和地理位置。此外,若是开启了Google实时,那么搜索结果会使用AJAX执行动态加载,而再也不须要提交表单。虽然URL中包含了不少参数,可是只有用于查询的参数q是必需的。
能够看到,搜索查询存储在输入参数q当中,而后表单提交到action属性设定的/search路径。咱们能够经过将test做为搜索条件提交给表单对其进行测试,此时会跳转到相似https://www.google.ro/?gws_ rd=cr,ssl&ei=TuXYWJXqBsGsswHO8YiQAQ#q=test&*的URL中。确切的URL取决于你的浏览器和地理位置。此外,若是开启了Google实时,那么搜索结果会使用AJAX执行动态加载,而再也不须要提交表单。虽然URL中包含了不少参数,可是只有用于查询的参数q是必需的。
当URL为https://www.google.com/search?q=test时,也能产生相同的搜索结果,如图1.2所示。
图1.2
搜索结果的结构可使用浏览器工具来查看,如图1.3所示。
图1.3
从图1.3中能够看出,搜索结果是以连接的形式出现的,而且其父元素是class为"r"的<h3>标签。
想要抓取搜索结果,咱们可使用第2章中介绍的CSS选择器。
1>>> from lxml.html import fromstring
2>>> import requests
3>>> html = requests.get('https://www.google.com/search?q=test')
4>>> tree = fromstring(html.content)
5>>> results = tree.cssselect('h3.r a')
6>>> results
7<Element a at 0x7f3d9affeaf8>,
8 <Element a at 0x7f3d9affe890>,
9 <Element a at 0x7f3d9affe8e8>,
10 <Element a at 0x7f3d9affeaa0>,
11 <Element a at 0x7f3d9b1a9e68>,
12 <Element a at 0x7f3d9b1a9c58>,
13 <Element a at 0x7f3d9b1a9ec0>,
14 <Element a at 0x7f3d9b1a9f18>,
15 <Element a at 0x7f3d9b1a9f70>,
16 <Element a at 0x7f3d9b1a9fc8>
到目前为止,咱们已经下载获得了Google的搜索结果,而且使用lxml抽取出其中的连接。在图1.3中,咱们发现连接中的真实网站URL以后还包含了一串附加参数,这些参数将用于跟踪点击。
下面是咱们在页面中找到的第一个连接。
1>>> link = results[0].get('href')
2>>> link
3 '/url?q=http://www.speedtest.net/&sa=U&ved=0ahUKEwiCqMHNuvbSAhXD6gTMAA&usg=
4 AFQjCNGXsvN-v4izEgZFzfkIvg'
这里咱们须要的内容是http://www.speedtest.net/,可使用urlparse模块从查询字符串中将其解析出来。
1>>> from urllib.parse import parse_qs, urlparse
2>>> qs = urlparse(link).query
3>>> parsed_qs = parse_qs(qs)
4>>> parsed_qs
5 {'q': ['http://www.speedtest.net/'],
6 'sa': ['U'],
7 'ved': ['0ahUKEwiCqMHNuvbSAhXD6gTMAA'],
8 'usg': ['AFQjCNGXsvN-v4izEgZFzfkIvg']}
9>>> parsed_qs.get('q', [])
10 ['http://www.speedtest.net/']
该查询字符串解析方法能够用于抽取全部连接。
1>>> links = []
2>>> for result in results:
3... link = result.get('href')
4... qs = urlparse(link).query
5... links.extend(parse_qs(qs).get('q', []))
6...
7>>> links
8 ['http://www.speedtest.net/',
9 'test',
10 'https://www.test.com/',
11 'https://ro.wikipedia.org/wiki/Test',
12 'https://en.wikipedia.org/wiki/Test',
13 'https://www.sri.ro/verificati-va-aptitudinile-1',
14 'https://www.sie.ro/AgentiaDeSpionaj/test-inteligenta.html',
15 'http://www.hindustantimes.com/cricket/india-vs-australia-live-cricket-scor
16 e-4th-test-dharamsala-day-3/story-8K124GMEBoiKOgiAaaB5bN.html',
17 'https://sports.ndtv.com/india-vs-australia-2017/live-cricket-score-india-v
18 s-australia-4th-test-day-3-dharamsala-1673771',
19 'http://pearsonpte.com/test-format/']
成功了!从Google搜索中获得的连接已经被成功抓取出来了。该示例的完整源码位于本书源码文件的chp9文件夹中,其名为scrape_google.py。
抓取Google搜索结果时会碰到的一个难点是,若是你的IP出现可疑行为,好比下载速度过快,则会出现验证码图像,如图1.4所示。
咱们能够下降下载速度,或者在必须高速下载时使用代理,以免被Google怀疑。过度请求Google会形成你的IP甚至是一个IP段被封禁,几个小时甚至几天没法访问Google的域名,因此请确保你可以礼貌地使用该网站,不会使你的家庭或办公室中的其余人(包括你本身)被列入黑名单。
图1.4
“Facebook”
为了演示浏览器和API的使用,咱们将会研究Facebook的网站。目前,从月活用户数维度来看,Facebook是世界上最大的社交网络之一,所以其用户数据很是有价值。
1.2.1 网站
图1.5所示为Packt出版社的Facebook页面。
当你查看该页的源代码时,能够找到最开始的几篇日志,可是后面的日志只有在浏览器滚动时才会经过AJAX加载。另外,Facebook还提供了一个移动端界面,正如第1章所述,这种形式的界面一般更容易抓取。该页面在移动端的展现形式如图1.6所示。
图1.5
图1.6
当咱们与移动端网站进行交互,并使用浏览器工具查看时,会发现该界面使用了和以前类似的结构来处理AJAX事件,所以该方法没法简化抓取。虽然这些AJAX事件能够被逆向工程,可是不一样类型的Facebook页面使用了不一样的AJAX调用,并且依据个人过往经验,Facebook常常会变动这些调用的结构,因此抓取这些页面须要持续维护。所以,如第5章所述,除非性能十分重要,不然最好使用浏览器渲染引擎执行JavaScript事件,而后访问生成的HTML页面。
下面的代码片断使用Selenium自动化登陆Facebook,并跳转到给定页面的URL。
1 from selenium import webdriver
2
3 def get_driver():
4 try:
5 return webdriver.PhantomJS()
6 except:
7 return webdriver.Firefox()
8
9 def facebook(username, password, url):
10 driver = get_driver()
11 driver.get('https://facebook.com')
12 driver.find_element_by_id('email').send_keys(username)
13 driver.find_element_by_id('pass').send_keys(password)
14 driver.find_element_by_id('loginbutton').submit()
15 driver.implicitly_wait(30)
16 # wait until the search box is available,
17 # which means it has successfully logged in
18 search = driver.find_element_by_name('q')
19 # now logged in so can go to the page of interest
20 driver.get(url)
21 # add code to scrape data of interest here ...
而后,能够调用该函数加载你感兴趣的Facebook页面,并使用合法的Facebook邮箱和密码,抓取生成的HTML页面。
1.2.2 Facebook API
抓取网站是在其数据没有给出结构化格式时的最末之选。而Facebook确实为绝大多数公共或私有(经过你的用户帐号)数据提供了API,所以咱们须要在构建增强的浏览器抓取以前,首先检查一下这些API提供的访问是否已经可以知足需求。
首先要作的事情是肯定经过API哪些数据是可用的。为了解决该问题,咱们须要先查阅其API文档。开发者文档的网址为https://developers.facebook.com/docs,在这里给出了全部不一样类型的API,包括图谱API,该API中包含了咱们想要的信息。若是你须要构建与Facebook的其余交互(经过API或SDK),能够随时查阅该文档,该文档会按期更新而且易于使用。
此外,根据文档连接,咱们还可使用浏览器内的图谱API探索工具,其地址为https://developers.facebook.com/tools/explorer/。如图1.7所示,探索工具是用来测试查询及其结果的很好的地方。
图1.7
在这里,我能够搜索API,获取PacktPub的Facebook页面ID。图谱探索工具还能够用来生成访问口令,咱们能够用它来定位API。
想要在Python中使用图谱API,咱们须要使用具备更高级请求的特殊访问口令。幸运的是,有一个名为facebook-sdk(https://facebook-sdk.readthedocs.io)的维护良好的库能够供咱们使用。咱们只需经过pip安装它便可。
1 pip install facebook-sdk
下面是使用Facebook的图谱API从Packt出版社页面中抽取数据的代码示例。
1 In [1]: from facebook import GraphAPI
2
3 In [2]: access_token = '....' # insert your actual token here
4
5 In [3]: graph = GraphAPI(access_token=access_token, version='2.7')
6
7 In [4]: graph.get_object('PacktPub')
8 Out[4]: {'id': '204603129458', 'name': 'Packt'}
咱们能够看到和基于浏览器的图谱探索工具相同的结果。咱们能够经过传递想要抽取的额外信息,来得到页面中的更多信息。要肯定使用哪些信息,咱们能够在图谱文档中看到页面中全部可用的字段,文档地址为https://developers.facebook.com/docs/graph-api/reference/page/。使用关键字参数fields,咱们能够从API中抽取这些额外可用的字段。
1 In [5]: graph.get_object('PacktPub', fields='about,events,feed,picture')
2 Out[5]:
3 'about': 'Packt provides software learning resources, from eBooks to video
4 courses, to everyone from web developers to data scientists.',
5 'feed': {'data': [{'created_time': '2017-03-27T10:30:00+0000',
6 'id': '204603129458_10155195603119459',
7 'message': "We've teamed up with CBR Online to give you a chance to win 5
8 tech eBooks - enter by March 31! http://bit.ly/2mTvmeA"},
9...
10 'id': '204603129458',
11 'picture': {'data': {'is_silhouette': False,
12 'url':
13'https://scontent.xx.fbcdn.net/v/t1.0-1/p50x50/14681705_10154660327349459_7
14 2357248532027065_n.png?oh=d0a26e6c8a00cf7e6ce957ed2065e430&oe=59660265'}}}
咱们能够看到该响应是格式良好的Python字典,咱们能够很容易地进行解析。
图谱API还提供了不少访问用户数据的其余调用,其文档能够从Facebook的开发者页面中获取,网址为https://developers.facebook.com/docs/graph-api。根据所需数据的不一样,你可能还须要建立一个Facebook开发者应用,从而得到可用时间更长的访问口令。
“Gap”
为了演示使用网站地图查看内容,咱们将使用Gap的网站。
Gap拥有一个结构化良好的网站,经过Sitemap能够帮助网络爬虫定位其最新的内容。若是咱们使用第1章中学到的技术调研该网站,则会发如今http://www.gap.com/robots.txt这一网址下的robots.txt文件中包含了网站地图的连接。
1 Sitemap: http://www.gap.com/products/sitemap_index.xml
下面是连接的Sitemap文件中的内容。
1<?xml version="1.0" encoding="UTF-8"?>
2<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3 <sitemap>
4 <loc>http://www.gap.com/products/sitemap_1.xml</loc>
5 <lastmod>2017-03-24</lastmod>
6 </sitemap>
7 <sitemap>
8 <loc>http://www.gap.com/products/sitemap_2.xml</loc>
9 <lastmod>2017-03-24</lastmod>
10 </sitemap>
11</sitemapindex>
如上所示,Sitemap连接中的内容不只仅是索引,其中又包含了其余Sitemap文件的连接。这些其余的Sitemap文件中则包含了数千种产品类目的连接,好比http://www.gap.com/products/womens-jogger- pants.jsp,如图1.8所示。
图1.8
这里有大量须要爬取的内容,所以咱们将使用第4章中开发的多线程爬虫。你可能还记得该爬虫支持URL模式以匹配页面。咱们一样能够定义一个scraper_callback关键字参数变量,可让咱们解析更多连接。
下面是爬取Gap网站中Sitemap连接的示例回调函数。
1 from lxml import etree
2 from threaded_crawler import threaded_crawler
3
4 def scrape_callback(url, html):
5 if url.endswith('.xml'):
6 # Parse the sitemap XML file
7 tree = etree.fromstring(html)
8 links = [e[0].text for e in tree]
9 return links
10 else:
11 # Add scraping code here
12 pass
该回调函数首先检查下载到的URL的扩展名。若是扩展名为.xml,则认为下载到的URL是Sitemap文件,而后使用lxml的etree模块解析XML文件并从中抽取连接。不然,认为这是一个类目URL,不过本例中尚未实现抓取类目的功能。如今,咱们能够在多线程爬虫中使用该回调函数来爬取gap.com了。
1 In [1]: from chp9.gap_scraper_callback import scrape_callback
2
3 In [2]: from chp4.threaded_crawler import threaded_crawler
4
5 In [3]: sitemap = 'http://www.gap.com/products/sitemap_index.xml'
6
7 In [4]: threaded_crawler(sitemap, '[gap.com]*',
8 scraper_callback=scrape_callback)
9 10
10 [<Thread(Thread-517, started daemon 140145732585216)>]
11 Exception in thread Thread-517:
12 Traceback (most recent call last):
13 ...
14 File "src/lxml/parser.pxi", line 1843, in lxml.etree._parseMemoryDocument
15 (src/lxml/lxml.etree.c:118282)
16 ValueError: Unicode strings with encoding declaration are not supported.
17 Please use bytes input or XML fragments without declaration.
不幸的是,lxml指望加载来自字节或XML片断的内容,而咱们存储的是Unicode的响应(由于这样可让咱们使用正则表达式进行解析,而且能够更容易地存储到磁盘中)。不过,咱们依然能够在本函数中访问该URL。虽然效率不高,可是咱们能够再次加载页面;若是咱们只对XML页面执行该操做,则能够减小请求的数量,从而不会增长太多加载时间。固然,若是咱们使用了缓存的话,也能够提升效率。
下面咱们将重写回调函数。
1 import requests
2
3 def scrape_callback(url, html):
4 if url.endswith('.xml'):
5 # Parse the sitemap XML file
6 resp = requests.get(url)
7 tree = etree.fromstring(resp.content)
8 links = [e[0].text for e in tree]
9 return links
10 else:
11 # Add scraping code here
12 pass
如今,若是咱们再次尝试运行,能够看到执行成功。
1 In [4]: threaded_crawler(sitemap, '[gap.com]*',
2 scraper_callback=scrape_callback)
3 10
4 [<Thread(Thread-51, started daemon 139775751223040)>]
5 Downloading: http://www.gap.com/products/sitemap_index.xml
6 Downloading: http://www.gap.com/products/sitemap_2.xml
7 Downloading: http://www.gap.com/products/gap-canada-fran?ais-index.jsp
8 Downloading: http://www.gap.co.uk/products/index.jsp
9 Skipping
10 http://www.gap.co.uk/products/low-impact-sport-bras-women-C1077315.jsp due
11 to depth Skipping
12 http://www.gap.co.uk/products/sport-bras-women-C1077300.jsp due to depth
13 Skipping
14 http://www.gap.co.uk/products/long-sleeved-tees-tanks-women-C1077314.jsp
15 due to depth Skipping
16 http://www.gap.co.uk/products/short-sleeved-tees-tanks-women-C1077312.jsp
17 due to depth ...
和预期一致,Sitemap文件首先被下载,而后是服装类目。在网络爬虫项目中,你会发现本身可能须要修改及调整代码和类,以适应新的问题。这只是从互联网上抓取内容时诸多使人兴奋的挑战之一。
本文摘自《用Python写网络爬虫(第2版)》
[德] 凯瑟琳,雅姆尔 著
史上首本Python网络爬虫图书全新升级,针对Python 3.x编写,提供示例完整源码和实例网站搭建源码。
讲解了如何使用Python来编写网络爬虫程序,内容包括网络爬虫简介,从页面中抓取数据的3种方法,提取缓存中的数据,使用多个线程和进程进行并发抓取,抓取动态页面中的内容,与表单进行交互,处理页面中的验证码问题,以及使用Scarpy和Portia进行数据抓取,并在最后介绍了使用本书讲解的数据抓取技术对几个真实的网站进行抓取的实例,旨在帮助读者活学活用书中介绍的技术。
小福利
关注【异步社区】服务号,转发本文至朋友圈或 50 人以上微信群,截图发送至异步社区服务号后台,并在文章底下留言你对python爬虫或者试读本书感觉,咱们将选出3名读者赠送《用Python写网络爬虫(第2版)》1本,赶快积极参与吧!(参与活动直达微信端Python爬虫技巧(文末福利))
活动截止时间:2018年8月2日
扫码关注咱们
在“异步社区”后台回复“关注”,便可免费得到2000门在线视频课程
阅读原文,购买《用Python写网络爬虫》