Python爬虫技巧

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:

​在本文中,我们将分析几个真实网站,来看看我们在《用Python写网络爬虫(第2版)》中学过的这些技巧是如何应用的。首先我们使用Google演示一个真实的搜索表单,然后是依赖JavaScript和API的网站Facebook,接下来是典型的在线商店Gap。由于这些都是活跃的网站,因此读者在阅读本书时这些网站存在已经发生变更的风险。

《用Python写网络爬虫(第2版)》

[德] 凯瑟琳,雅姆尔 著

不过这样也好,因为本文示例的目的是为了向你展示如何应用前面所学的技术,而不是展示如何抓取任何网站。当你选择运行某个示例时,首先需要检查网站结构在示例编写后是否发生过改变,以及当前该网站的条款与条件是否禁止了爬虫。

在本文中,我们将介绍如下主题:

抓取Google搜索结果网页;

调研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写网络爬虫(第2版)》

[德] 凯瑟琳,雅姆尔 著

史上首本Python网络爬虫图书全新升级,针对Python 3.x编写,提供示例完整源码和实例网站搭建源码。

讲解了如何使用Python来编写网络爬虫程序,内容包括网络爬虫简介,从页面中抓取数据的3种方法,提取缓存中的数据,使用多个线程和进程进行并发抓取,抓取动态页面中的内容,与表单进行交互,处理页面中的验证码问题,以及使用Scarpy和Portia进行数据抓取,并在最后介绍了使用本书讲解的数据抓取技术对几个真实的网站进行抓取的实例,旨在帮助读者活学活用书中介绍的技术。

小福利

关注【异步社区】服务号,转发本文至朋友圈或 50 人以上微信群,截图发送至异步社区服务号后台,并在文章底下留言你对python爬虫或者试读本书感受,我们将选出3名读者赠送《用Python写网络爬虫(第2版)》1本,赶快积极参与吧!(参与活动直达微信端Python爬虫技巧(文末福利)
活动截止时间:2018年8月2日

扫码关注我们

在“异步社区”后台回复“关注”,即可免费获得2000门在线视频课程

文章转载自公众号

阅读原文,购买《用Python写网络爬虫》

阅读原文

相关文章
|
28天前
|
数据采集 存储 XML
Python爬虫定义入门知识
Python爬虫是用于自动化抓取互联网数据的程序。其基本概念包括爬虫、请求、响应和解析。常用库有Requests、BeautifulSoup、Scrapy和Selenium。工作流程包括发送请求、接收响应、解析数据和存储数据。注意事项包括遵守Robots协议、避免过度请求、处理异常和确保数据合法性。Python爬虫强大而灵活,但使用时需遵守法律法规。
|
11天前
|
数据采集 存储 XML
Python爬虫:深入探索1688关键词接口获取之道
在数字化经济中,数据尤其在电商领域的价值日益凸显。1688作为中国领先的B2B平台,其关键词接口对商家至关重要。本文介绍如何通过Python爬虫技术,合法合规地获取1688关键词接口,助力商家洞察市场趋势,优化营销策略。
|
29天前
|
数据采集 缓存 定位技术
网络延迟对Python爬虫速度的影响分析
网络延迟对Python爬虫速度的影响分析
|
1月前
|
数据采集 Web App开发 监控
高效爬取B站评论:Python爬虫的最佳实践
高效爬取B站评论:Python爬虫的最佳实践
|
1月前
|
数据采集 存储 JSON
Python网络爬虫:Scrapy框架的实战应用与技巧分享
【10月更文挑战第27天】本文介绍了Python网络爬虫Scrapy框架的实战应用与技巧。首先讲解了如何创建Scrapy项目、定义爬虫、处理JSON响应、设置User-Agent和代理,以及存储爬取的数据。通过具体示例,帮助读者掌握Scrapy的核心功能和使用方法,提升数据采集效率。
90 6
|
8天前
|
数据采集 JSON 开发者
Python爬虫京东商品详情数据接口
京东商品详情数据接口(JD.item_get)提供商品标题、价格、品牌、规格、图片等详细信息,适用于电商数据分析、竞品分析等。开发者需先注册账号、创建应用并申请接口权限,使用时需遵循相关规则,注意数据更新频率和错误处理。示例代码展示了如何通过 Python 调用此接口并处理返回的 JSON 数据。
|
13天前
|
XML 数据采集 数据格式
Python 爬虫必备杀器,xpath 解析 HTML
【11月更文挑战第17天】XPath 是一种用于在 XML 和 HTML 文档中定位节点的语言,通过路径表达式选取节点或节点集。它不仅适用于 XML,也广泛应用于 HTML 解析。基本语法包括标签名、属性、层级关系等的选择,如 `//p` 选择所有段落标签,`//a[@href=&#39;example.com&#39;]` 选择特定链接。在 Python 中,常用 lxml 库结合 XPath 进行网页数据抓取,支持高效解析与复杂信息提取。高级技巧涵盖轴的使用和函数应用,如 `contains()` 用于模糊匹配。
|
15天前
|
数据采集 XML 存储
构建高效的Python网络爬虫:从入门到实践
本文旨在通过深入浅出的方式,引导读者从零开始构建一个高效的Python网络爬虫。我们将探索爬虫的基本原理、核心组件以及如何利用Python的强大库进行数据抓取和处理。文章不仅提供理论指导,还结合实战案例,让读者能够快速掌握爬虫技术,并应用于实际项目中。无论你是编程新手还是有一定基础的开发者,都能在这篇文章中找到有价值的内容。
|
14天前
|
数据采集 JavaScript 前端开发
Python爬虫能处理动态加载的内容吗?
Python爬虫可处理动态加载内容,主要方法包括:使用Selenium模拟浏览器行为;分析网络请求,直接请求API获取数据;利用Pyppeteer控制无头Chrome。这些方法各有优势,适用于不同场景。
|
21天前
|
数据采集 监控 搜索推荐
python爬虫的基本使用
本文介绍了Python爬虫的基本概念及其广泛应用,包括搜索引擎、数据挖掘、网络监控、舆情分析和信息聚合等领域。通过安装`urllib`和`BeautifulSoup`库,展示了如何编写简单代码实现网页数据的抓取与解析。爬虫技术在大数据时代的重要性日益凸显,为各行业提供了高效的数据获取手段。
31 1