Python 中可以進行網頁解析的庫有很多,常見的有 BeautifulSoup 和 lxml 等。在網上玩爬蟲的文章通常都是介紹 BeautifulSoup 這個庫,我平常也是常用這個庫,最近用 Xpath 用得比較多,使用 BeautifulSoup 就不大習慣,很久之前就知道 Reitz 大神出了一個叫 Requests-HTML 的庫,一直沒有興趣看,這回可算歹著機會用一下了。
使用pip install requests-html安裝,上手和 Reitz 的其他庫一樣,輕松簡單:
from requests_html import HTMLSession
session = HTMLSession()
r = session.get('https://www.python.org/jobs/')
這個庫是在 requests 庫上實現的,r 得到的結果是 Response 對象下面的一個子類,多個一個html 的屬性。所以 requests 庫的響應對象可以進行什么操作,這個 r 也都可以。如果需要解析網頁,直接獲取響應對象的 html 屬性:
r.html
不得不膜拜 Reitz 大神太會組裝技術了。實際上 HTMLSession 是繼承自 requests.Session 這個核心類,然后將 requests.Session 類里的 requests 方法改寫,返回自己的一個 HTMLResponse 對象,這個類又是繼承自 requests.Response,只是多加了一個_from_response 的方法來構造實例:
class HTMLSession(requests.Session):
# 重寫 request 方法,返回 HTMLResponse 構造
def request(self, *args, **kwargs) -> HTMLResponse:
r = super(HTMLSession, self).request(*args, **kwargs)
return HTMLResponse._from_response(r, self)
class HTMLResponse(requests.Response):
# 構造器
@classmethod
def _from_response(cls, response, session: Union['HTMLSession', 'AsyncHTMLSession']):
html_r = cls(session=session)
html_r.__dict__.update(response.__dict__)
return html_r
之后在 HTMLResponse 里定義屬性方法 html,就可以通過 html 屬性訪問了,實現也就是組裝 PyQuery 來干。核心的解析類也大多是使用 PyQuery 和 lxml 來做解析,簡化了名稱,挺討巧的。
元素定位可以選擇兩種方式:
# css 獲取有多少個職位
jobs = r.html.find("h1.call-to-action")
# xpath 獲取
jobs = r.html.xpath("//h1[@class='call-to-action']")
方法名非常簡單,符合 Python 優雅的風格,這里不妨對這兩種方式簡單的說明:
定位到元素以后勢必要獲取元素里面的內容和屬性相關數據,獲取文本:
jobs.text
jobs.full_text
獲取元素的屬性:
attrs = jobs.attrs
value = attrs.get("key")
還可以通過模式來匹配對應的內容:
## 找某些內容匹配
r.html.search("Python {}")
r.html.search_all()
這個功能看起來比較雞肋,可以深入研究優化一下,說不定能在 github 上混個提交。
除了一些基礎操作,這個庫還提供了一些人性化的操作。比如一鍵獲取網頁的所有超鏈接,這對于整站爬蟲應該是個福音,URL 管理比較方便:
r.html.absolute_links
r.html.links
內容頁面通常都是分頁的,一次抓取不了太多,這個庫可以獲取分頁信息:
print(r.html)
# 比較一下
for url in r.html:
print(url)
結果如下:
# print(r.html)
<HTML url='https://www.python.org/jobs/'>
# for
<HTML url='https://www.python.org/jobs/'>
<HTML url='https://www.python.org/jobs/?page=2'>
<HTML url='https://www.python.org/jobs/?page=3'>
<HTML url='https://www.python.org/jobs/?page=4'>
<HTML url='https://www.python.org/jobs/?page=5'>
通過迭代器實現了智能發現分頁,這個迭代器里面會用一個叫_next 的方法,貼一段源碼感受下:
def get_next():
candidates = self.find('a', containing=next_symbol)
for candidate in candidates:
if candidate.attrs.get('href'):
# Support 'next' rel (e.g. reddit).
if 'next' in candidate.attrs.get('rel', []):
return candidate.attrs['href']
通過查找 a 標簽里面是否含有指定的文本來判斷是不是有下一頁,通常我們的下一頁都會通過下一頁 或者加載更多 來引導,他就是利用這個標志來進行判斷。默認的以列表形式存在全局:['next','more','older']。我個人認為這種方式非常不靈活,幾乎沒有擴展性。感興趣的可以往 github 上提交代碼優化。
也許是考慮到了現在 js 的一些異步加載,這個庫支持 js 運行時,官方說明如下:
Reloads the response in Chromium, and replaces HTML content
with an updated version, with JavaScript executed.
使用非常簡單,直接調用以下方法:
r.html.render()
第一次使用的時候會下載 Chromium,不過國內你懂的,自己想辦法去下吧,就不要等它自己下載了。render 函數可以使用 js 腳本來操作頁面,滾動操作單獨做了參數。這對于上拉加載等新式頁面是非常友好的。
Reitz 大神設計出來的東西還是一如既往的簡單好用,自己不多做,大多用別人的東西組裝,簡化 api。真是夠人性。不過有的地方還是優化空間,希望有興趣和精力的童鞋去 github 上關注一下這個項目。
多朋友都聽說過Python的大名,而Python也擁有眾多的爬蟲框架,其中最簡單的莫過于requests-html了。它和著名的網絡請求庫requests是同一個作者,著重于XML數據提取,可以說是最簡單的爬蟲框架了。
安裝這個類庫非常簡單,直接通過pip就可以安裝了。
pip install requests-html
requests-html用起來也十分簡單,下面是一個簡單例子。照例說明一下,第一段引入了HTMLSession用于創建連接,獲取網頁數據。第二段創建連接,獲取了我的簡書用戶頁面。第三段用xpath語法獲取了網頁上的用戶名,最后打印出來。
from requests_html import HTMLSession
session = HTMLSession()
response = session.get(
'https://www.jianshu.com/u/7753478e1554')
username = response.html.xpath(
'//a[@class="name"]/text()', first=True)
print(username)
看起來是不是很簡單?沒錯,確實很簡單,接下來還有一些更加有趣的功能。
編寫爬蟲之前還要做一件事情,就是分析網頁的結構。這個工作其實也很簡單,打開你要訪問的網頁,按F12打開開發人員工具,可以看到最左邊有這么一個按鈕。點擊這個按鈕,然后點擊網頁上你想要查看的網頁元素,然后你就可以發現這個元素對應的相關源代碼已經為你定位完畢了。
定位按鈕
通過這個功能,我們就可以輕松的分析網頁,然后通過它的結構來編寫爬蟲了。
上面的response.html即是網頁的根節點HTML節點,在節點對象上可以調用一些方法來檢索數據。最常用的方法是find方法,它通過CSS選擇器來定位數據。對于上面的例子,可以用find方法改寫第三段。
因為所有查找方法返回的結果都是列表,所以如果你確定只需要查找一個,就將first參數設為真來只返回第一個結果。find方法返回的仍然是一個節點,如果只需要節點的內容,調用其text屬性即可。
用戶名對應的HTML結構如圖所示。
代碼如下。
username = response.html.find('a.name', first=True).text
除了find方法之外,還可以使用xpath方法用xpath語法來查找節點,正如第一個例子那樣。我個人比較喜歡xpath語法,CSS選擇器雖然更加流行一些,但是寫出來的效果有點怪,不如xpath工整。
同樣是這個頁面,看看如何獲取我的簡書的個人簡介。網頁代碼如圖所示。
代碼如下。
description = response.html.xpath(
'//div[@class="description"]/div[@class="js-intro"]/text()', first=True)
CSS選擇器和XPATH語法都不是本篇的主要內容,如果你這方面不太熟悉,最好去看一下相關的教程。當然如果大家有什么疑問的話,也可以提出來。假如大家想看的話,我也可以專門寫一篇文章介紹一下這些語法知識。
有些網頁利用了前后端分離技術開發的,需要瀏覽器渲染才能完整顯示。如果用爬蟲去看的話,只能顯示一部分內容。這時候就需要瀏覽器渲染頁面,才能獲取完整的頁面。用requests-html的話,這個過程非常簡單。
首先先來看看一個需要渲染網頁的例子。下面的代碼訪問了我的簡書用戶頁面,然后嘗試獲取我的所有文章。但是如果你運行這個例子的話,就會發現只能獲取前幾項。因為簡書的頁面正是一個典型的需要瀏覽器渲染的頁面,爬蟲獲取到的網頁是不完整的。
from requests_html import HTMLSession
session = HTMLSession()
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.119 Safari/537.36'
}
url = 'https://www.jianshu.com/u/7753478e1554'
r = session.get(url, headers=headers)
for a in r.html.xpath('//ul[@class="note-list"]/li/div[@class="content"]/a[@class="title"]'):
title = a.text
link = f'https://www.jianshu.com{a.attrs["href"]}'
print(f'《{title}》,{link}')
那么如何渲染網頁來獲取完整的結果呢?其實非常簡單,在查詢HTML節點之前,調用render函數即可。
render函數來使用瀏覽器渲染
原理也非常簡單,第一次調用render的時候,requests-html會在本地下載一個chromium瀏覽器,用它來渲染網頁。如此一來,我們就可以獲取到渲染之后的頁面了。
但是對于簡書這個例子來說還是有些問題,因為如果你在瀏覽器里打開這個網頁的話,會發現一些文章在瀏覽器下滑頁面的時候才開始渲染。不過聰慧的作者早就考慮到這種情況了,render函數支持下滑的參數,設定之后,就會模擬瀏覽器下滑操作,從而解決了這個問題。
r.html.render(scrolldown=50, sleep=0.2)
不論上面的r.html還是find/xpath函數返回的結果,它們都是節點對象。除了上面介紹的幾個提取數據的方法以外,節點對象還有以下一些屬性,在我們提取數據的時候也有很大作用。
相較于專業的爬蟲框架scrapy,或者僅用于解析XML的解析庫BeautifulSoup。requests-html可以是說恰到好處,它沒有前者那么難學,也不像后者還需要搭配HTTP請求庫才能使用。如果你手頭需要臨時抓取幾個網頁,那么requests-html就是你最好的選擇。
urllib庫參考:Python 爬蟲之urllib庫
Requests是用python語言基于urllib編寫的,該模塊主要用來發 送 HTTP 請求,requests 模塊比 urllib 模塊更簡潔,是學習 python 爬蟲的較好的http請求模塊。
不是 python 的內置庫,如果沒有安裝,可以安裝先。
pip install requests
requests里提供個各種請求方式,每次調用 requests 請求之后,會返回一個 response 對象,該對象包含了具體的響應信息。響應信息如下:
1、基本Get 請求
import requests
x = requests.get('http://www.baidu.com')
print(x.status_code)
print(x.reason)
print(x.apparent_encoding)
print(x.text)
執行結果:
200
OK
utf-8
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8>.....
請求 json 數據文件,返回 json 內容:
通過params參數傳遞一個字典內容,從而直接構造url
import requests
params = {
'name': 'zhangsan',
'age': 100
}
x = requests.get('http://httpbin.org/get',params=params)
print(x.url)
print(x.json())
執行結果:
http://httpbin.org/get?name=zhangsan&age=100
{'args': {'age': '100', 'name': 'zhangsan'}, 'headers': {'...
抓取二進制數據
在上面的例子中,我們抓取的是網站的一個頁面,實際上它返回的是一個 HTML 文檔。如果想抓取圖片、音頻、視頻等文件,需要用到content,這樣獲取的數據是二進制數據。
如抓取百度logo圖片:
import requests
x = requests.get('https://www.baidu.com/img/flexible/logo/pc/result.png')
print(x.content)
執行結果:
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\xca\x00\
得到的是一串二進制的亂碼,如果想得到圖片,直接將其保存到本地即可。
import requests
x = requests.get('https://www.baidu.com/img/flexible/logo/pc/result.png')
with open('baidulogo.png','wb') as f:
f.write(x.content)
執行后,在本地目錄,可以看到圖片已經下載
添加headers
有些網頁,必須需要headers才能訪問。這時只需要將參賽傳到 headers參數中即可,如下所示:
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'
}
x = requests.get('https://baidu.com/', headers=headers)
print(x.text)
2、基本POST請求
在爬蟲中,另外一個比較常見的請求方式就是 POST 請求,跟GET用法差不多。
post() 方法可以發送 POST 請求到指定 url,一般格式如下:
requests.post(url, data={key: value}, json={key: value}, args)
import requests
data = {'name': 'zhangsan', 'age': '100'}
x = requests.post("http://httpbin.org/post", data=data)
print(x.text)
執行結果:
{
"args": {},
"data": "",
"files": {},
"form": {
"age": "100",
"name": "zhangsan"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Content-Length": "21",
....
3、其他請求
其他請求可以自行測試,差不多用法。
r = requests.put('https://httpbin.org/put', data = {'key':'value'})
r = requests.delete('https://httpbin.org/delete')
r = requests.head('https://httpbin.org/get')
r = requests.options('https://httpbin.org/get')
requests 方法如下表:
import requests
# 發送請求
x = requests.request('get', 'https://httpbin.org/')
# 返回網頁內容
print(x.status_code)
執行結果:
200
設置請求頭:
# 導入 requests 包
import requests
kw = {'s':'python 教程'}
# 設置請求頭
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"}
# params 接收一個字典或者字符串的查詢參數,字典類型自動轉換為url編碼,不需要urlencode()
response = requests.get("https://httpbin.org", params = kw, headers = headers)
# 查看響應狀態碼
print (response.status_code)
# 查看響應頭部字符編碼
print (response.encoding)
# 查看完整url地址
print (response.url)
# 查看響應內容,response.text 返回的是Unicode格式的數據
print(response.text)
執行結果:
200
utf-8
https://httpbin.org/?s=python+%E6%95%99%E7%A8%8B
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>httpbin.org</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700"
rel="stylesheet">
....
1、文件上傳
實現方法和其他參數類似,也是構造一個字典然后通過files參數傳遞。
import requests
files= {"files":open("git.jpeg","rb")}
response = requests.post("http://httpbin.org/post",files=files)
print(response.text)
2、獲取cookie
import requests
response = requests.get("http://www.baidu.com")
print(response.cookies)
for key,value in response.cookies.items():
print(key+"="+value)
3、會話維持
cookie的一個作用就是可以用于模擬登陸,做會話維持。
import requests
s = requests.Session()
s.get("http://httpbin.org/cookies/set/number/123456")
response = s.get("http://httpbin.org/cookies")
print(response.text)
4、證書驗證
現在的很多網站都是https的方式訪問,所以這個時候就涉及到證書的問題,有時遇到:requests.exceptions.SSLError: HTTPSConnectionPool 錯誤。
import requests
from requests.packages import urllib3
urllib3.disable_warnings()
response = requests.get("https://httpbin.org",verify=False)
print(response.status_code)
當然,我們也可以指定一個本地證書用作客戶端證書,這可以是單個文件(包含密鑰和證書)或一個包含兩個文件路徑的元組:
import requests
response = requests.get('https://httpbin.org/', cert=('/path/server.crt', '/path/server.key'))
print(response.status_code)
上面的代碼是演示實例,我們需要有 crt 和 key 文件,并且指定它們的路徑。另外注意,本地私有證書的 key 必須是解密狀態,加密狀態的 key是不支持的。
5、代理設置
import requests
from requests.auth import HTTPBasicAuth
response = requests.get("http://192.168.152.100:9001/",auth=HTTPBasicAuth("user","123"))
print(response.status_code)
還有一種方式
import requests
response = requests.get("http://192.168.152.100:9001/",auth=("user","123"))
print(response.status_code)
6、超時設置
在某些網絡情況不好的情況下,服務器可能很久才會響應甚至無法響應,此時就需要設置超時來避免無限的等待。在 requests 中,我們可以通過 timeout 參數來設置超時時間,這個時間是發出請求到服務器返回的整個過程所用的時間,即連接和讀取兩個過程,單位為秒。當然,也可以分別設置連接和讀取各自的超時時間:
import requests
r = requests.get('https://httpbin.org/get', timeout=1)
print(r.status_code)
r = requests.get('https://httpbin.org/get', timeout=(5, 30))
當然,如果不設置 timeout 或者將其設置為 None ,則表示不會進行超時處理,即會永久等待下去。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。