爬蟲系列-request基本庫的使用

上一篇博客中,我們瞭解了urllib的基本用法,但是它還是存在很多缺陷,比如處理網頁驗證和Cookies時,需要寫通過類來實例化對象比較麻煩。爲了更加方便地實現這些操作,就有了更爲強大的庫requests,對於它來說Cookies、登錄驗證、代理設置等操作都容易很多。

1.基本用法

它如何簡單我下面舉個例子你們可以看看:

import requests
response = requests.get('http://www.baidu.com/')
print(response.text)

在這裏插入圖片描述
短短几行也就把百度首頁的源代碼獲取下來了,是不是很簡單,這是一個很小的部分而已。我們還可以看獲取的網頁狀態碼等一些其他信息。

print(response.status_code)
print(type(response))
print(r.cookies)
print(type(r.text))

最終得到的結果:

200
<class 'requests.models.Response'>
<RequestsCookieJar[]>
<class 'str'>

我們可看到狀態碼是正常的,然後返回的文本信息也是字符串類型的。也可以查看請求的Response的類型。
requests使用get()方法完成了GET請求,這倒不算什麼,更方便在與請求請求,比如POST,PUT,DELETE等請求,下面我們可以看看POST請求。

response = requests.post('http://www.iqianyue.com/mypost')
print(response.text)

返回的結果:

<html>
<head>
<title>Post Test Page</title>
</head>
<body>
<form action="" method="post">
name:<input name="name" type="text" /><br>
passwd:<input name="pass" type="text" /><br>
<input name="" type="submit" value="submit" />
<br />
</body>
</html>

是不是很簡單,其實這是一小點而已,post(),put(),delete()這三個函數就能直接實現對上面三種方式的請求。

get請求

HTTP中最常見的請求之一就是GET請求,下面首先來詳細瞭解一下利用requests構建GET請求的方法。直接用request.get()就可以了。上面我也舉過例子了。

那麼,對於GET請求,如果要附加額外的信息,一般怎樣添加呢?比如現在想添加兩個參數,其中name是germey,age是22。要構造這個請求鏈接,是不是要直接寫成:

r = requests.get('http://httpbin.org/get?name=germey&age=22')

這樣看起來很不人性化,而且構造的網址靈活性差。一般情況下,這種信息數據會用字典來存儲。我們可以這樣來構造:

import requests
data = {
    'name': 'germey',
    'age': 22
}
r = requests.get("http://httpbin.org/get", params=data)
print(r.text)

最終返回的結果是:

{
  "args": {
    "age": "22", 
    "name": "germey"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.18.4"
  }, 
  "origin": "113.57.74.32, 113.57.74.32", 
  "url": "https://httpbin.org/get?name=germey&age=22"
}

通過運行結果可以看到url構造成我們想要的形式了。http://httpbin.org/get?age=22&name=germey
另外,網頁的返回類型實際上是str類型,但是它很特殊,是JSON格式的。所以,如果想直接解析返回結果,得到一個字典格式的話,可以直接調用json()方法。

import requests
r = requests.get("http://httpbin.org/get")
print(type(r.text))
print(r.json())
print(type(r.json()))

運行結果是這樣的:

<class 'str'>
{'headers': {'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.10.0'}, 'url': 'http://httpbin.org/get', 'args': {}, 'origin': '182.33.248.131'}
<class 'dict'>

但前提是返回結果是json()格式,如果不是這種格式直接解析的話會出現問題。
下面我們來寫一兩個簡單的爬取讓大家看看request如何進行get請求爬取並解析。

小實例

1.文本數據爬取

比如我們以知乎的一個頁面爲例https://www.zhihu.com/explore爬取下面的問題

import requests
from  lxml  import  etree

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'
}
r = requests.get("https://www.zhihu.com/explore", headers=headers)
selector =etree.HTML(r.text)
title_list = selector.xpath('//div[@class="ExploreRoundtableCard-questionItem"]/a/text()')
print(title_list)

lxml 是一個解析庫,後面我會專門單獨來講,通過上面這簡單的幾行代碼,我就能完成基礎的爬取,基本成功的提取了所有問題的內容,返回的結果是:

['信號頻域和時域的關係是怎樣的?',
 '什麼是信號?',
 '信號與系統這門課程有何價值?',
 '爲什麼機器人研究了幾十年,還是給人感覺沒有太大進展?',
 '這種不考試,以娛樂爲主的機器人教育對於中小學生及幼兒的意義何在?',
 '人形機器人的研究的意義是什麼?',
 '公司招聘時號稱彈性工作制,入職後部門領導卻要求打卡上班,應該怎麼辦?',
 '對於工作 5 年以上的職場人來說,有哪些比工資低更危險的事?',
 '在你的行業裏,有哪些看似「不職業」的行爲其實是「非常職業」的表現?',
 '申請國外 PhD 的時候如何與教授套磁?',
 '科研工作者有哪些「新手常見錯誤」?',
 '如何成爲一個不油膩的科研工作者?']
2.二進制數據爬取

在上面的例子中,我們抓取的是知乎的一個頁面,實際上它返回的是一個HTML文檔。如果想抓去圖片、音頻、視頻等文件,應該怎麼辦呢?圖片、音頻、視頻這些文件本質上都是由二進制碼組成的,由於有特定的保存格式和對應的解析方式,我們纔可以看到這些形形色色的多媒體。所以,想要抓取它們,就要拿到它們的二進制碼。
舉個例子https://github.com/favicon.ico這個是github的一個圖標,當我們直接請求,然後打印返回的結果會亂碼,因爲我們直接打印會將圖片直接轉化爲字符串肯定會亂碼,我們下面這樣做就沒問題,而且在當前路徑下回生成一個圖標樣式的文件。

import requests
r = requests.get("https://github.com/favicon.ico")
with open('favicon.ico', 'wb') as f:
    f.write(r.content)

在我們上面請求中,第一個知乎中添加了headers,也就是請求頭,我們需要在請求頭中添加user-agent,不然很有可能請求不到。也可以在headers這個參數中添加任意的其他字段信息。

5.響應

在python請求中,我們要通過返回的狀態碼對響應結果進行分析,查看是出了什麼問題,下面我列出所有的狀態碼大家可以看一下:

# 信息性狀態碼
100: ('continue',),
101: ('switching_protocols',),
102: ('processing',),
103: ('checkpoint',),
122: ('uri_too_long', 'request_uri_too_long'),

# 成功狀態碼
200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\\o/', '✓'),
201: ('created',),
202: ('accepted',),
203: ('non_authoritative_info', 'non_authoritative_information'),
204: ('no_content',),
205: ('reset_content', 'reset'),
206: ('partial_content', 'partial'),
207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'),
208: ('already_reported',),
226: ('im_used',),

# 重定向狀態碼
300: ('multiple_choices',),
301: ('moved_permanently', 'moved', '\\o-'),
302: ('found',),
303: ('see_other', 'other'),
304: ('not_modified',),
305: ('use_proxy',),
306: ('switch_proxy',),
307: ('temporary_redirect', 'temporary_moved', 'temporary'),
308: ('permanent_redirect',
      'resume_incomplete', 'resume',), # These 2 to be removed in 3.0

# 客戶端錯誤狀態碼
400: ('bad_request', 'bad'),
401: ('unauthorized',),
402: ('payment_required', 'payment'),
403: ('forbidden',),
404: ('not_found', '-o-'),
405: ('method_not_allowed', 'not_allowed'),
406: ('not_acceptable',),
407: ('proxy_authentication_required', 'proxy_auth', 'proxy_authentication'),
408: ('request_timeout', 'timeout'),
409: ('conflict',),
410: ('gone',),
411: ('length_required',),
412: ('precondition_failed', 'precondition'),
413: ('request_entity_too_large',),
414: ('request_uri_too_large',),
415: ('unsupported_media_type', 'unsupported_media', 'media_type'),
416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'),
417: ('expectation_failed',),
418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'),
421: ('misdirected_request',),
422: ('unprocessable_entity', 'unprocessable'),
423: ('locked',),
424: ('failed_dependency', 'dependency'),
425: ('unordered_collection', 'unordered'),
426: ('upgrade_required', 'upgrade'),
428: ('precondition_required', 'precondition'),
429: ('too_many_requests', 'too_many'),
431: ('header_fields_too_large', 'fields_too_large'),
444: ('no_response', 'none'),
449: ('retry_with', 'retry'),
450: ('blocked_by_windows_parental_controls', 'parental_controls'),
451: ('unavailable_for_legal_reasons', 'legal_reasons'),
499: ('client_closed_request',),

# 服務端錯誤狀態碼
500: ('internal_server_error', 'server_error', '/o\\', '✗'),
501: ('not_implemented',),
502: ('bad_gateway',),
503: ('service_unavailable', 'unavailable'),
504: ('gateway_timeout',),
505: ('http_version_not_supported', 'http_version'),
506: ('variant_also_negotiates',),
507: ('insufficient_storage',),
509: ('bandwidth_limit_exceeded', 'bandwidth'),
510: ('not_extended',),
511: ('network_authentication_required', 'network_auth', 'network_authentication')

6.文件上傳

我們知道requests可以模擬瀏覽器提交一些數據,加入有的網站需要上傳文件,我們也可以用它來實現:

import requests
files = {'file': open('favicon.ico', 'rb')}
r = requests.post("http://httpbin.org/post", files=files)
print(r.text)

返回的結果大家可以在自己電腦上看。

7.Cookies

前面我們使用urllib處理過Cookies,寫法比較複雜,而有了requests,獲取和設置Cookies只需一步即可完成。

import requests
r = requests.get("http://tieba.baidu.com/f?ie=utf-8&kw=%E6%B9%96%E5%8C%97%E7%BB%8F%E6%B5%8E%E5%AD%A6%E9%99%A2")
print(r.cookies)
for key, value in r.cookies.items():
    print(key + '=' + value)

運行結果如下:

<RequestsCookieJar[<Cookie BAIDUID=691FAAA9A1B2BCC389C3803DE3F3A7DE:FG=1 for .baidu.com/>, <Cookie TIEBA_USERTYPE=3fad14137434bb1707615468 for .tieba.baidu.com/>]>
BAIDUID=691FAAA9A1B2BCC389C3803DE3F3A7DE:FG=1
TIEBA_USERTYPE=3fad14137434bb1707615468

這裏我們首先調用cookies屬性即可成功得到Cookies,可以發現它是RequestCookieJar類型。然後用items()方法將其轉化爲元組組成的列表,遍歷輸出每一個Cookie的名稱和值,實現Cookie的遍歷解析。
當然我們也可以將自己登陸後的cookies來維持登錄狀態,下面還是用知乎爲例

import requests
headers = {
    'Cookie': '_zap=fb088aab-4271-4702-9f79-a83cd3b241b1; _xsrf=21713a2f-9a52-4c69-b5a0-cbc42a04b641; d_c0="AIAi3k8pEhCPTtIPkdiPzE_HPvUWm1JHut0=|1568876696"; tgw_l7_route=4860b599c6644634a0abcd4d10d37251; capsion_ticket="2|1:0|10:1568881198|14:capsion_ticket|44:ZDAzNjlmNmE3NTBhNDQyNDllNjUwZTY2NjNhMzEzZGQ=|79ba5fb5fbee8187a4f17d962e1fc8e313ae9ce3e3574857ad7c23df7bf5c00d"; l_n_c=1; r_cap_id="MzY2MmE1OWQ1ZDkwNDU3MTg0OWFlYzA3NGE0YzQ1MTg=|1568881210|22639adc0478eb7daaf98a39b322fdde3c4b886a"; cap_id="ZmZjYTY2YTZiMzgyNDEwNjgxYWQzNzQxYzgwMmNjMzM=|1568881210|d0f484488bf9ff8393b986c989b0ad9431b781eb"; l_cap_id="NWY5YzgzZWRkOTNlNDJlNmEwZWE2NTQ5MGNkYzQ0YjE=|1568881210|c8fc82e6624979ff0aa88fdd0458a9023b19df2a"; n_c=1; z_c0=Mi4xNDU1cEJnQUFBQUFBZ0NMZVR5a1NFQmNBQUFCaEFsVk5RWWh3WGdCdEd1SzgyYTJCV0RCYTNTN3ZsSXRJSG9PRFVB|1568881217|0f2d335543b8478a74b018564a8d27b7bf914f3b; tst=r',
    'Host': 'www.zhihu.com',
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36',
}
r = requests.get('https://www.zhihu.com', headers=headers)
print(r.text)

這樣我們就實現了用cookies保持登錄狀態,這裏的cookies換成自己的試試:
在這裏插入圖片描述

8.會話維持

在requests中,如果直接利用get()或post()等方法的確可以做到模擬網頁的請求,但是這實際上是相當於不同的會話,也就是說相當於你用了兩個瀏覽器打開了不同的頁面。
設想這樣一個場景,第一個請求利用post()方法登錄了某個網站,第二次想獲取成功登錄後的自己的個人信息,你又用了一次get()方法去請求個人信息頁面。實際上,這相當於打開了兩個瀏覽器,是兩個完全不相關的會話,能成功獲取個人信息嗎?那當然不能。其實解決這個問題的主要方法就是維持同一個會話,也就是相當於打開一個新的瀏覽器選項卡而不是新開一個瀏覽器。但是我又不想每次設置cookies,那該怎麼辦呢?這時候就有了新的利器——Session對象。
這裏我們請求了一個測試網址http://httpbin.org/cookies/set/number/123456789。請求這個網址時,可以設置一個cookie,名稱叫作number,內容是123456789,隨後又請求了http://httpbin.org/cookies,此網址可以獲取當前的Cookies。

import requests
requests.get('http://httpbin.org/cookies/set/number/123456789')
r = requests.get('http://httpbin.org/cookies')
print(r.text)

我們希望獲取到cookies,返回的結果:

{
  "cookies": {}
}

這並不行。我們再用Session試試看

import requests

s = requests.Session()
s.get('http://httpbin.org/cookies/set/number/123456789')
r = s.get('http://httpbin.org/cookies')
print(r.text)

返回結果是:

{
  "cookies": {
    "number": "123456789"
  }
}

成功獲取!這下能體會到同一個會話和不同會話的區別了吧!

所以,利用Session,可以做到模擬同一個會話而不用擔心Cookies的問題。它通常用於模擬登錄成功之後再進行下一步的操作。

Session在平常用得非常廣泛,可以用於模擬在一個瀏覽器中打開同一站點的不同頁面。

9.SSL證書驗證

此外,requests還提供了證書驗證的功能。當發送HTTP請求的時候,它會檢查SSL證書,我們可以使用verify參數控制是否檢查此證書。其實如果不加verify參數的話,默認是True,會自動驗證。

前面我們提到過,12306的證書沒有被官方CA機構信任,會出現證書驗證錯誤的結果。我們現在訪問它,都可以看到一個證書問題的頁面。
我們需要修改它的verify參數就正常了。

import requests

response = requests.get('https://www.12306.cn', verify=False)
print(response.status_code)

在這裏插入圖片描述
但它會警告我們讓我們給它指定證書,我們也可以忽略。我們也可以上傳一個本地證書,我下面給出一個樣例格式:

import requests

response = requests.get('https://www.12306.cn', cert=('/path/server.crt', '/path/key'))
print(response.status_code)

當然,上面的代碼是演示實例,我們需要有crt和key文件,並且指定它們的路徑。注意,本地私有證書的key必須是解密狀態,加密狀態的key是不支持的。後面有具體的例子我再來給大家講解。

10.代理設置

對於某些網站,在測試的時候請求幾次,能正常獲取內容。但是一旦開始大規模爬取,對於大規模且頻繁的請求,網站可能會彈出驗證碼,或者跳轉到登錄認證頁面,更甚者可能會直接封禁客戶端的IP,導致一定時間段內無法訪問。

那麼,爲了防止這種情況發生,我們需要設置代理來解決這個問題,這就需要用到proxies參數。可以用這樣的方式設置:

import requests

proxies = {
  "http": "http://10.10.1.10:3128",
  "https": "http://10.10.1.10:1080",
}

requests.get("https://www.taobao.com", proxies=proxies)

這一塊內容我們後面具體實踐用到我在詳細講解,而且現在大多用的是那個API付費接口的代理,嵌入比較簡單技術性要求不是很高,大家瞭解就行啦。最後在和大家提下超時設置,前面urllib也講過了,實際差不多。

超時設置

在本機網絡狀況不好或者服務器網絡響應太慢甚至無響應時,我們可能會等待特別久的時間纔可能收到響應,甚至到最後收不到響應而報錯。爲了防止服務器不能及時響應,應該設置一個超時時間,即超過了這個時間還沒有得到響應,那就報錯。這需要用到timeout參數。這個時間的計算是發出請求到服務器返回響應的時間。示例如下:

import requests

r = requests.get("https://www.taobao.com", timeout = 1)
print(r.status_code)

返回200表示請求正常。實際上,請求分爲兩個階段,即連接(connect)和讀取(read)。

上面設置的timeout將用作連接和讀取這二者的timeout總和。

如果要分別指定,就可以傳入一個元組:

r = requests.get('https://www.taobao.com', timeout=(5,11, 30))

如果想永久等待,可以直接將timeout設置爲None,或者不設置直接留空,因爲默認是None。這樣的話,如果服務器還在運行,但是響應特別慢,那就慢慢等吧,它永遠不會返回超時錯誤的。其用法如下:

r = requests.get('https://www.taobao.com', timeout=None)
#r = requests.get('https://www.taobao.com')

當我們把這些大致瞭解,基本的request庫用法也差不多了,剩下如果還有什麼問題可以去官網查看。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章