在本文中,咱們將分析幾個真實網站,來看看咱們在《用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寫網絡爬蟲》