程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

Python爬蟲遇上動態加載

編輯:Python

Python爬蟲遇上動態加載

  • 1.通過示例認識動態加載
  • 2.JavaScript 逆向工程
  • 3.渲染動態頁面
  • 4.更加自動化的渲染----Selenium
      • 1.驅動下載與設置
      • 2. 小示例了解Selenium
      • 3.了解Selenium的定位

我想大家在使用爬蟲爬取數據的過程中遇到過如下的情況吧,明明在網頁源碼看得到需要的內容,而且各種節點也沒問題,可是就是爬取不到想要的數據,這其實就是現在大多數網頁使用動態渲染(JavaScript),這種頁面不再是加載後立即下載頁面全部內容,許多網頁在浏覽器中展示的內容可能不會出現在 HTML 源代碼中。這就是我們遇到上述情況的原因。

我們在這裡介紹兩種解決動態加載的方法,一種是JavaScript 逆向工程,另一種是渲染 JavaScript。

1.通過示例認識動態加載

首先,讓我們看看什麼樣的是動態加載。示例使用網址 http://example.python-scraping.com/search,比如我們查找A開頭的國家,如下:


我們通過開發者工具查看源碼,可以看到我們需要的內容在id=results節點下:


接著,我們編寫提取數據的代碼,代碼如下:

import re
import requests
from bs4 import BeautifulSoup
import lxml.html
import time
from lxml.html import fromstring
#獲取網頁內容
def download(url,user_agent='wswp',proxy=None,num_retries=2):
print ('Downloading:',url)
headers = {
'User-Agent': user_agent}
try:
resp = requests.get(url, headers=headers, proxies=proxy)
html = resp.text
if resp.status_code >= 400:
print('Download error:', resp.text)
html = None
if num_retries and 500 <= resp.status_code < 600:
# recursively retry 5xx HTTP errors 
return download(url, num_retries - 1)
except requests.exceptions.RequestException as e:
print('Download error:', e.reason)
html = None
return html
# 提取需要的內容
html = download('http://example.python-scraping.com/search')
tree = fromstring(html)
tree.cssselect('div#results a')
# 輸出結果
Downloading: http://example.python-scraping.com/search
[]

從結果看得出,我們並沒有獲取到我們想要的東西,這是為啥呢,這就是動態加載,打開代碼下載的html源碼,是找不到我們需要的東西的。結果如下:

看到這裡,估計大家對動態渲染有了一定的認識,下面我們介紹如何獲取這種頁面內容。

2.JavaScript 逆向工程

我們使用之前的抓取方法,無法抓取到動態加載的頁面數據,那想要抓取這部分數據,我們就得了解這種頁面是如何加載數據的,該過程就描述為逆向工程。

繼續前面的例子,我們在浏覽器工具中單擊 Network選項卡,然後執行一次搜索,我們將會看到對於給定頁面的所有請求,大多數請求都是圖片,其中有個search.json的文件,單擊我們發現裡面包含我們需要的所有數據,如下圖:


很容易發現,上圖的結果其實是一個json響應,這個東西不僅可以在浏覽器中訪問,還可以直接下載,響應代碼如下:

import requests
resp = requests.get('http://example.python-scraping.com/places/ajax/search.json?&search_term=A&page_size=10&page=0')
resp.json()
# 結果輸出
{
'records': [{
'pretty_link': '<div><a href="/places/default/view/Afghanistan-1"><img src="/places/static/images/flags/af.png" /> Afghanistan</a></div>',
'country_or_district': 'Afghanistan',
'id': 7969417},
{
'pretty_link': '<div><a href="/places/default/view/Aland-Islands-2"><img src="/places/static/images/flags/ax.png" /> Aland Islands</a></div>',
'country_or_district': 'Aland Islands',
'id': 7969418},
{
'pretty_link': '<div><a href="/places/default/view/Albania-3"><img src="/places/static/images/flags/al.png" /> Albania</a></div>',
'country_or_district': 'Albania',
'id': 7969419},
{
'pretty_link': '<div><a href="/places/default/view/Algeria-4"><img src="/places/static/images/flags/dz.png" /> Algeria</a></div>',
'country_or_district': 'Algeria',
'id': 7969420},
...}

上面的代碼中,我們通過requests 庫的 json 方法訪問了JSON響應,我們還可以下載原始字符串響應,然後使用json.load進行加載。但是上面的代碼有個問題是我們只獲取了包含A字母的內容,那如何獲取全部的呢?當然可以解決,AJAX使用正則表達式進行匹配,所以我們只需要將url中的search_term=A換成`search_term=.就可以加載全部的數據。

另外,因為url中page_size=10,所以一頁中只有十個數據,這個參數AJAX並不會檢查,所以我們可以給一個很大的數,是的所有數據一次性下載完成。

最終的代碼如下:

from csv import DictWriter
import requests
template_url = 'http://example.python-scraping.com/places/ajax/search.json?&search_term=.&page_size=1000&page=0'
resp = requests.get(template_url)
data = resp.json()
records = data.get('records')
with open('./countries_or_districts.csv', 'w') as countries_or_districts_file:
wrtr = DictWriter(countries_or_districts_file, fieldnames=records[0].keys())
wrtr.writeheader()
wrtr.writerows(records)

運行上述代碼,就可以在響應文件夾下得到如下的數據表:

3.渲染動態頁面

對於實際中,某一些網站我們能夠快速地對 API 的方法進行逆向工程來了解它如何工作,以及如何使用它在一個請求中獲取結果。但是,一些網站非常復雜,即使使用高級的浏覽器工具也很難理解。這類網站難以實施逆向工程。這就得用到我們這裡要講解到的東西----渲染動態頁面。

盡管經過足夠的努力,任何網站都可以被逆向工程,不過我們可以使用浏覽器渲染引擎避免這些工作,這種渲染引擎是浏覽器在顯示網頁時解析HTML、應用 CSS 樣式並執行 JavaScript 語句的部分。在本節中,我們將使用QWebEngineView渲染引擎,通過 Qt 框架可以獲得該引擎的一個便捷 Python 接口。

我們可以使用位於http://example.python-scraping.com/dynamic 上的這個簡單示例,來演示使用Qt渲染。

常規提取:

import lxml.html
url = 'http://example.python-scraping.com/dynamic'
html = download(url)
tree = lxml.html.fromstring(html)
tree.cssselect('#result')[0].text_content()
# 輸出:
Downloading: http://example.python-scraping.com/dynamic
''

使用Qt框架:

import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtWebEngineWidgets import QWebEngineView
import lxml.html
class Render(QWebEngineView): # 子類Render繼承父類QWebEngineView
def __init__(self, url):
self.html = ''
self.app = QApplication(sys.argv)
super().__init__()
self.loadFinished.connect(self._loadFinished)
self.load(QUrl(url))
self.app.exec_()
def _loadFinished(self):
self.page().toHtml(self.callable)
def callable(self, data):
self.html = data
self.app.quit()
if __name__ == '__main__':
url = 'http://example.python-scraping.com/dynamic'
r = Render(url)
result = r.html
tree = lxml.html.fromstring(result)
a = tree.cssselect('#result')[0].text_content()
print(a)
# 結果輸出
Hello World

這裡得說明一下,使用Jupyter Notebook會出現ImportError: QtWebEngineWidgets must be imported before a QCoreApplication instance is created的錯誤,所以我這裡是使用PyCharm演示的,關於上述代碼,說明如下:

  • class Render(QWebEngineView):構建Render類,繼承自QWebEngineView
  • self.app = QApplication(sys.argv):初始化QApplication對象
  • self.loadFinished.connect(self._loadFinished):指定加載完成後的操作。這裡綁定自定義函數_loadFinished
  • self.page().toHtml(self.callable):這是通過回調函數返回html。沒有回調函數會報錯
  • callable(self, data):回調函數,用於把html返回給外部

4.更加自動化的渲染----Selenium

使用前面小節中的 QWebEngineView,我們可以自定義浏覽器渲染引擎,這樣就能完全控制想要執行的行為。如果不需要這麼高的靈活性,那麼還有一個不錯的更容易安裝的替代品 Selenium 可以選擇,它提供的 API 接口可以自動化處理多個常見浏覽器。

1.驅動下載與設置

Selenium 就是模仿人操作浏覽器,它可操作的浏覽器有多種,比如Firefox (FirefoxDriver)、IE (InternetExplorerDriver)、Opera (OperaDriver) 和 Chrome (ChromeDriver)等,除此之外,它還支持 Android (AndroidDriver)和 iPhone (IPhoneDriver) 的移動應用測試,而且還包括一個基於 HtmlUnit 的無界面實現。本文只講谷歌驅動,其他有需要自行百度。

因為它是操作浏覽器,所以需要下載相應浏覽器的驅動,而且,版本也得相同。比如:我現在使用的谷歌浏覽器版本是 版本 91.0.4472.124,所以我下載的驅動也必須是91版本開頭的驅動,否則無法使用。
谷歌浏覽器驅動下載地址:http://npm.taobao.org/mirrors/chromedriver/

下載好驅動後,解壓,把解壓文件放置在Anaconda\Scripts 目錄下,當然如果你用的不是Anaconda,可以放到相應的位置。說白了,這一步就是把驅動放置在系統環境變量的path路徑下,完全可以直接把驅動所在的地址添加在環境變量path中。

添加環境變量

2. 小示例了解Selenium

from selenium import webdriver
driver = webdriver.Chrome()
# 傳入url
driver.get("http://www.baidu.com")
# 傳遞參數,讓百度搜索Selenium2
driver.find_element_by_id("kw").send_keys("Selenium2")
# 找到百度一下按鈕,點擊一下
driver.find_element_by_id("su").click()
# 等待10秒
time.sleep(10)
# 關閉浏覽器
driver.quit()

到這裡算是了解也體驗了selenium的運行機制,下次學習如何定位元素,如何傳遞參數。

3.了解Selenium的定位

既然要模擬人操作浏覽器,那總得知道輸入什麼,點哪裡?這種問題就是它的定位,selenium有多種定位方式,分別是元素定位、Xpath定位和Css定位,每種方式都有自己的特點,實際使用時,可以多種方式混合使用。

  • 元素的定位類似於找人,可以通過人本身的屬性查找,例如姓名、身份證號、性別等等,這些類似於web頁面的id、name、class name等;
  • 除了上述的找人方法還可以有通過地址找,比如某國、某市、某路、某小區,這種方式類似於web頁面的XPath和css方法。
  • 還有一種方法就是通過相關的人找,比如我要找小明,但是沒有他的電話號碼,但是我有小明爸爸的,我就可以通過小明爸爸找到小明的聯系方式,繼而找到小明,這種方法也可以在XPath和CSS中找到

篇幅問題,則合理不再細講相應的操作,網上一搜一大堆,感興趣的自行百度。不過下面有個我當時學習的Jupyter 文件,供大家參考。

Selenium 入門 Jupyter演示


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved