Python爬蟲技巧

Python爬蟲技巧

在本文中,我們將分析幾個真實網站,來看看我們在《用Python寫網絡爬蟲(第2版)》中學過的這些技巧是如何應用的。首先我們使用Google演示一個真實的搜索表單,然後是依賴JavaScript和API的網站Facebook,接下來是典型的在線商店Gap。由於這些都是活躍的網站,因此讀者在閱讀本書時這些網站存在已經發生變更的風險。

Python爬蟲技巧

《用Python寫網絡爬蟲(第2版)》

[德] 凱瑟琳,雅姆爾 著

不過這樣也好,因為本文示例的目的是為了向你展示如何應用前面所學的技術,而不是展示如何抓取任何網站。當你選擇運行某個示例時,首先需要檢查網站結構在示例編寫後是否發生過改變,以及當前該網站的條款與條件是否禁止了爬蟲。

在本文中,我們將介紹如下主題:

抓取Google搜索結果網頁;

調研Facebook的API;

在Gap網站中使用多線程;

“Google搜索引擎”

為了瞭解我們對CSS選擇器知識的使用情況,我們將會抓取Google的搜索結果。根據中Alexa的數據,Google是全世界最流行的網站之一,而且非常方便的是,該網站結構簡單,易於抓取。

圖1.1所示為Google搜索主頁使用瀏覽器工具加載查看錶單元素時的界面。

Python爬蟲技巧

圖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所示。

Python爬蟲技巧

圖1.2

搜索結果的結構可以使用瀏覽器工具來查看,如圖1.3所示。

Python爬蟲技巧

圖1.3

從圖1.3中可以看出,搜索結果是以鏈接的形式出現的,並且其父元素是class為"r"的

標籤。

想要抓取搜索結果,我們可以使用第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,

8 ,

9 ,

10 ,

11 ,

12 ,

13 ,

14 ,

15 ,

16

到目前為止,我們已經下載得到了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的域名,所以請確保你能夠禮貌地使用該網站,不會使你的家庭或辦公室中的其他人(包括你自己)被列入黑名單。

Python爬蟲技巧

圖1.4

“Facebook”

為了演示瀏覽器和API的使用,我們將會研究Facebook的網站。目前,從月活用戶數維度來看,Facebook是世界上最大的社交網絡之一,因此其用戶數據非常有價值。

1.2.1 網站

圖1.5所示為Packt出版社的Facebook頁面。

當你查看該頁的源代碼時,可以找到最開始的幾篇日誌,但是後面的日誌只有在瀏覽器滾動時才會通過AJAX加載。另外,Facebook還提供了一個移動端界面,正如第1章所述,這種形式的界面通常更容易抓取。該頁面在移動端的展示形式如圖1.6所示。

Python爬蟲技巧

圖1.5

Python爬蟲技巧

圖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所示,探索工具是用來測試查詢及其結果的很好的地方。

Python爬蟲技巧

圖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

2

3

4 http://www.gap.com/products/sitemap_1.xml

5 2017-03-24

6

7

8 http://www.gap.com/products/sitemap_2.xml

9 2017-03-24

10

11

如上所示,Sitemap鏈接中的內容不僅僅是索引,其中又包含了其他Sitemap文件的鏈接。這些其他的Sitemap文件中則包含了數千種產品類目的鏈接,比如http://www.gap.com/products/womens-jogger- pants.jsp,如圖1.8所示。

Python爬蟲技巧

圖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 []

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 []

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寫網絡爬蟲(第2版)》

[德] 凱瑟琳,雅姆爾 著

史上首本Python網絡爬蟲圖書全新升級,針對Python 3.x編寫,提供示例完整源碼和實例網站搭建源碼。

講解了如何使用Python來編寫網絡爬蟲程序,內容包括網絡爬蟲簡介,從頁面中抓取數據的3種方法,提取緩存中的數據,使用多個線程和進程進行併發抓取,抓取動態頁面中的內容,與表單進行交互,處理頁面中的驗證碼問題,以及使用Scarpy和Portia進行數據抓取,並在最後介紹了使用本書講解的數據抓取技術對幾個真實的網站進行抓取的實例,旨在幫助讀者活學活用書中介紹的技術。

小福利

關注【異步社區】服務號,轉發本文至朋友圈或 50 人以上微信群,截圖發送至異步社區服務號後臺,並在文章底下留言你對python爬蟲或者試讀本書感受,我們將選出3名讀者贈送《用Python寫網絡爬蟲(第2版)》1本,趕快積極參與吧!(參與活動直達微信端https://mp.weixin.qq.com/s/AR4YQ8kcCu4iFTBru13Asg)

活動截止時間:2018年8月2日

在“異步社區”後臺回覆“關注”,即可免費獲得2000門在線視頻課程

閱讀https://www.epubit.com/book/detail/33225,購買《用Python寫網絡爬蟲》


分享到:


相關文章: