爬蟲之網路請求中的那些事
urllib庫
urllib庫是python自帶的內建庫,不需要安裝
urllib庫是Python中一個最基本的網路請求庫。可以模擬瀏覽器的行為,向指定的伺服器傳送一個請求,並可以儲存伺服器返回的資料
在Python3的urllib庫中,所有和網路請求相關的方法,都被集到urllib.request模組中
request中常用的方法
urlopen函式
urlopen函式用來建立一個表示遠端url的類檔案物件,然後像本地檔案一樣操作這個類檔案物件來獲取遠端資料
部分原始碼
def urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
*, cafile=None, capath=None, cadefault=False, context=None):
'''Open the URL url, which can be either a string or a Request object.
*data* must be an object specifying additional data to be sent to
the server, or None if no such data is needed. See Request for
details.
'''
引數詳解
- url:請求的url
- data:請求的data,如果設定了這個值,將變成post請求
- 返回值:返回值是一個http.client.HTTPResponse物件,這個物件是一個類檔案控制程式碼物件。有read(size)、readline、readlines以及getcode等方法
控制程式碼如何理解?
數值上,是一個32位無符號整型值(32位系統下);邏輯上,相當於指標的指標;形象理解上,是Windows中各個物件的一個唯一的、固定不變的ID;作用上,Windows使用控制程式碼來標識諸如視窗、點陣圖、畫筆等物件,並通過控制程式碼找到這些物件
from urllib import request
'''
Open the URL url, which can be either a string or a Request object
'''
resp = request.urlopen('https://www.sogou.com/')
print(resp) # 返回一個HTTPresponse物件,<http.client.HTTPResponse object at 0x000002E0CADA0C50>
print(resp.read()) # 返回網頁原始碼
print(resp.read(1024)) # 返回1024位元組大小的資料
print(resp.readline()) # 返回一行資料
print(resp.readlines()) # 返回多行資料
print(resp.getcode()) # 返回狀態碼:200
urlretrieve函式
這個函式可以方便的將網頁上的一個檔案儲存到本地。以下程式碼可以非常方便的將搜狗的首頁下載到本地:
from urllib import request
request.urlretrieve('https://www.sogou.com/','sogou.html')
部分原始碼
def urlretrieve(url, filename=None, reporthook=None, data=None):
"""
Retrieve a URL into a temporary location on disk.
Requires a URL argument. If a filename is passed, it is used as
the temporary file location. The reporthook argument should be
a callable that accepts a block number, a read size, and the
total file size of the URL target. The data argument should be
valid URL encoded data.
If a filename is passed and the URL points to a local resource,
the result is a copy from local file to new file.
Returns a tuple containing the path to the newly created
data file as well as the resulting HTTPMessage object.
"""
格式
request.urlretrieve(url,檔名)
示例
from urllib import request
request.urlretrieve('https://th.bing.com/th/id/OIP.NX9KpjV4Mk_e72YV_6nPNgDhEs?w=182&h=243&c=7&r=0&o=5&dpr=1.25&pid=1.7','haizei.jpg')
urlencode、parse_qs函式
urlencode可以把字典資料轉換為URL編碼的資料,如果我們使用瀏覽器傳送一個請求的時候,url中攜帶漢字,那麼瀏覽器就會自動進行了url編碼
如何解碼?使用parse_qs函式就可以解碼了
部分原始碼
def urlencode(query, doseq=False, safe='', encoding=None, errors=None,
quote_via=quote_plus):
'''Encode a dict or sequence of two-element tuples into a URL query string.'''
def parse_qs(qs, keep_blank_values=False, strict_parsing=False,
encoding='utf-8', errors='replace', max_num_fields=None):
'''Parse a query given as a string argument.'''
示例1
from urllib import parse
data = {'name': 'Hammer', 'age': 18}
# 編碼
res = parse.urlencode(data)
print(res) # name=Hammer&age=18
# 解碼
ret = parse.parse_qs(res)
print(ret) # {'name': ['Hammer'], 'age': ['18']}
示例2
瀏覽器會幫助我們進行url編碼,那麼我們使用pycharm進行爬取資料的時候,中文需要我們自己處理
from urllib import parse,request
data1 = {'q':'古力娜扎'}
res1 = parse.urlencode(data1)
print(res1,type(res1)) # q=%E5%8F%A4%E5%8A%9B%E5%A8%9C%E6%89%8E <class 'str'>
res = request.urlopen('https://www.bing.com/search?'+res1)
print(res.read())
示例3
這裡需要注意的是,url字串編碼和encode方法編碼的結果是不一樣的
data = {'Name':'古力娜扎'}
res2 = parse.urlencode(data)
print(res2) # Name=%E5%8F%A4%E5%8A%9B%E5%A8%9C%E6%89%8E
qs = parse.quote('古力娜扎')
print(qs) # %E5%8F%A4%E5%8A%9B%E5%A8%9C%E6%89%8E
print('古力娜扎'.encode('utf8')) # b'\xe5\x8f\xa4\xe5\x8a\x9b\xe5\xa8\x9c\xe6\x89\x8e'
urlparse、urlsplit函式:
有時候拿到一個url,想要對這個url的各個組成部分進行分割,那麼這個時候使用urlparse或者urlsplit來進行分割;
URL組成:
- 協議部分:http/https
- 域名部分:www.baidu.com / ip地址
- 埠部分:跟在域名後面,以
:
作為分隔符,不寫的時候使用埠,比如127.0.0.1:8000 - 虛擬目錄部分:從域名後的第一個“/”開始到最後一個“/”為止,是虛擬目錄部分。虛擬目錄也不是一個URL必須的部分。比如:127.0.0.1:8000/backend/3.html,backend就是虛擬目錄
- 檔名部分:從域名後的最後一個“/”開始到“?”為止,是檔名部分,如果沒有
“?”
,則是從域名後的最後一個“/”開始到“#”
為止,是檔案部分,如果沒有“?”
和“#”
,那麼從域名後的最後一個“/”
開始到結束,都是檔名部分。檔名部分也不是一個URL必須的部分,如果省略該部分,則使用預設的檔名 - 錨部分:從“#”開始到最後,都是錨部分
- 引數部分:從
?
開始到#
為止之間的部分為有參部分,又稱為搜尋部分或者查詢部分,引數允許有多個,一般用&
做分隔符
部分原始碼
# urlsplit函式
def urlsplit(url, scheme='', allow_fragments=True):
'''
Parse a URL into 5 components:
<scheme>://<netloc>/<path>?<query>#<fragment>
Return a 5-tuple: (scheme, netloc, path, query, fragment).
'''
# urlparse函式
def urlparse(url, scheme='', allow_fragments=True):
"""Parse a URL into 6 components:
<scheme>://<netloc>/<path>;<params>?<query>#<fragment>
Return a 6-tuple: (scheme, netloc, path, params, query, fragment).
Note that we don't break the components up in smaller bits
(e.g. netloc is a single string) and we don't expand % escapes."""
注意
通過原始碼註釋的對比,我們發現urlparse返回的引數比urlsplit返回的引數多一個params
,params是url中的可選引數
示例
from urllib import parse
res = parse.urlparse('https://zzk.cnblogs.com/my/s/blogpost-p?Keywords=python')
ret = parse.urlsplit('https://zzk.cnblogs.com/my/s/blogpost-p?Keywords=python')
print(res,type(res))
print(ret,type(ret))
'''
結果:
ParseResult(scheme='https', netloc='zzk.cnblogs.com', path='/my/s/blogpost-p', params='', query='Keywords=python', fragment='') <class 'urllib.parse.ParseResult'>
SplitResult(scheme='https', netloc='zzk.cnblogs.com', path='/my/s/blogpost-p', query='Keywords=python', fragment='') <class 'urllib.parse.SplitResult'>
'''
# 返回的是一個物件,那麼可以通過點的方法獲取到該物件的屬性值
print(res.scheme,res.params)
request.Request類
Request類也是用於處理網路請求,如果想要在請求的時候增加一些請求頭,那麼就必須使用request.Request類來實現,比如要增加一個User-Agent
部分原始碼:
class Request:
def __init__(self, url, data=None, headers={},
origin_req_host=None, unverifiable=False,
method=None):
通過原始碼我們可以看到,必傳的引數有url和headers(傳字典)
from urllib import request
header = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39'
}
res = request.Request('https://www.cnblogs.com/',headers=header)
print(res) # <urllib.request.Request object at 0x000001959F347CF8>
ret = request.urlopen(res)
print(ret.read()) # 返回網頁原始碼
我們可以通過新增UA來獲取更多的網頁原始碼··
實戰:爬取貓眼票房資料
from urllib import request
import json
url = 'https://piaofang.maoyan.com/dashboard-ajax?orderType=0&uuid=7cba1a83-0143-4e91-b431-6e48cf2c4f57&timeStamp=1647662149259&User-Agent=TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzk5LjAuNDg0NC41MSBTYWZhcmkvNTM3LjM2IEVkZy85OS4wLjExNTAuMzk%3D&index=442&channelId=40009&sVersion=2&signKey=fb7aa416ff3c852bfe69b01ee7952272'
header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39'
}
ret = request.Request(url,headers=header)
resp = request.urlopen(ret)
# print(resp.get('movieList').get('data').get('list').decode('utf8'))
data = resp.read().decode('utf8')
'''注意這裡獲取的是json格式資料'''
data = json.loads(data)
data_list = data.get('movieList').get('data').get('list')
for line in data_list:
print(line.get('movieInfo'))
'''結果'''
{'movieId': 341955, 'movieName': '新蝙蝠俠', 'releaseInfo': '上映2天'}
{'movieId': 338380, 'movieName': '神祕海域', 'releaseInfo': '上映6天'}
{'movieId': 1446115, 'movieName': '長津湖之水門橋', 'releaseInfo': '上映47天'}
{'movieId': 1405863, 'movieName': '熊出沒·重返地球', 'releaseInfo': '上映47天'}
{'movieId': 1433696, 'movieName': '這個殺手不太冷靜', 'releaseInfo': '上映47天'}
{'movieId': 1300821, 'movieName': '花束般的戀愛', 'releaseInfo': '上映26天'}
{'movieId': 1383307, 'movieName': '奇蹟·笨小孩', 'releaseInfo': '上映47天'}
{'movieId': 1435147, 'movieName': '我們的冬奧', 'releaseInfo': '上映29天'}
{'movieId': 1309268, 'movieName': '可不可以你也剛好喜歡我', 'releaseInfo': '上映9天'}
{'movieId': 1265626, 'movieName': '印度女孩', 'releaseInfo': '上映2天'}
{'movieId': 1367251, 'movieName': '狙擊手', 'releaseInfo': '上映47天'}
{'movieId': 1445469, 'movieName': '喜羊羊與灰太狼之筐出未來', 'releaseInfo': '上映47天'}
{'movieId': 1303796, 'movieName': '“煉”愛', 'releaseInfo': '上映2天'}
{'movieId': 1405621, 'movieName': '瑪納斯人之失落的祕境', 'releaseInfo': '上映首日'}
{'movieId': 1214749, 'movieName': '紐約的一個雨天', 'releaseInfo': '上映23天'}
{'movieId': 1302341, 'movieName': '*小道', 'releaseInfo': ''}
{'movieId': 1247341, 'movieName': '妖怪手錶:永遠的朋友', 'releaseInfo': '上映8天'}
{'movieId': 1396517, 'movieName': '年少有你', 'releaseInfo': '上映2天'}
{'movieId': 1338396, 'movieName': '如果有一天我將會離開你', 'releaseInfo': '上映9天'}
實戰:爬取別逗了網站資訊
from urllib import request,parse
for page in range(9270,9273):
header = {
'user - agent': 'Mozilla / 5.0(Windows NT 10.0;Win64; x64) AppleWebKit /537.36KHTML, likeGecko) Chrome/99.0.4844.51 Safari / 537.36 Edg / 99.0.1150.39'
}
url = f'https://www.biedoul.com/index/{page}/'
request.Request(url=url,headers=header)
res = request.urlopen(url)
request.urlretrieve(url,f'{page}.html')
page_data = res.read().decode('utf8')
print(page_data)
ProxyHandler處理器(代理設定)
很多時候我們玩遊戲開外掛,飛天遁地透視····最後被封號~我們爬蟲也類似,老是不斷的去請求,人家也會煩,封你的ip;
在我們爬取資料的時候如果對伺服器請求的次數過多的時候,伺服器也不傻會識別出來是非正常請求,一般採取我措施是封IP措施,這時候我們的IP就不能正常訪問,可以通過“換小號繼續玩”;
?檢視http請求的一些引數:http://httpbin.org
檢視沒有使用代理的ip地址:
http://httpbin.org/ip
from urllib import request
# No Proxy
url = 'http://httpbin.org/ip'
resp = request.urlopen(url)
print(resp.read()) # b'{\n "origin": "112.64.68.252"\n}\n'
設定代理ip
通過設定代理ip解決封ip問題,原理是通過請求代理伺服器,然後通過代理伺服器請求目標伺服器,然後返回資料
常用的代理有:
- 西刺免費代理IP:http://www.xicidaili.com/
- 快代理:http://www.kuaidaili.com/
- 代理雲:http://www.dailiyun.com/
from urllib import request
# Use Proxy
url = 'http://httpbin.org/ip'
# 使用ProxyHandler,傳入代理構造一個handler
handler = request.ProxyHandler({'http':'106.15.197.250:8001'})
# 使用上面的handler構造opener
opener = request.build_opener(handler)
# 使用opener傳送請求
resp = opener.open(url)
print(resp.read()) # b'{\n "origin": "106.15.197.250"\n}\n'
'''我們可以看到ip並不是以前的ip了,這樣就設定成功了,但是缺點就是免費的不穩定'''
Cookie
什麼是cookie?
指某些網站為了辨別使用者身份、進行 session 跟蹤而儲存在使用者本地終端上的資料,cookie儲存的資料量有限,不同的瀏覽器有不同的儲存大小,但一般不超過4KB。因此使用cookie只能儲存一些小量的資料;
我們知道HTTP的四大特徵:
- 基於請求響應:服務端永遠不會主動給客戶端發訊息 必須是客戶端先發請求,如果想讓服務端主動給客戶端傳送訊息可以採用其他網路協議;
- 基於TCP/UDP、IP作用於應用層之上的協議
- 無狀態:不儲存客戶端的狀態資訊,不知道是屬於哪一個使用者發過來的請求,每次過來都彷彿第一次看見(待你如初戀)
- 無連線/短連線:兩者請求響應之後立刻斷絕關係
- Cookie的出現就是為了解決無狀態,用來辨別身份登入一次,下一次來攜帶Cookie就知道是誰了;
cookie的格式
Set-Cookie: NAME=VALUE;Expires/Max-age=DATE;Path=PATH; Domain=DOMAIN_NAME;SECURE
引數意義:
- NAME:cookie的名字。
- VALUE:cookie的值。
- Expires:cookie的過期時間。
- Path:cookie作用的路徑。
- Domain:cookie作用的域名。
- SECURE:是否只在https協議下起作用。
實戰:爬蟲使用Cookie實現模擬登入
from urllib import request
url = 'https://www.zhihu.com/hot'
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36',
'cookie': '_zap=8812b5e0-0b83-4ce1-9ac5-c913debd77c5; d_c0="AIDel4RQJxSPTp9hk0DqVuFjlrklYMgxsHM=|1639015736"; _9755xjdesxxd_=32; YD00517437729195%3AWM_TID=cfvcktVDh9REFEQRAVJ%2B97fYipUcBdd5; Hm_lvt_98beee57fd2ef70ccdd5ca52b9740c49=1642496751,1642507682,1642515429,1642954789; _xsrf=3S5Nqf0sEQkI3Cln9pmAI6lgYnk6Wxoh; __snaker__id=IKbmra7CqY74Sn2D; YD00517437729195%3AWM_NI=utbfMp3td5HSlvkH%2FYeFIlzsYQABzUsxftqGf3FISbVKXYoqU0oCNKXIYIGU4%2Bb1V3MvuX1jFJyMVSE8dQ2rCDro8DGNd1ZNcs%2BOCpPc8OSf67cfOoqOnWaAR%2B3MfelJdkU%3D; YD00517437729195%3AWM_NIKE=9ca17ae2e6ffcda170e2e6ee9aae429789fa90e44083b08ab7c55e828b9aaaf87efba899a9ef658ca681d2fb2af0fea7c3b92ab7acae87d07cf1acacd6cb59b0aaa5a9ae53a79a878df9799b8f8fa3f95fb1a8aa95f44da6ed8aaad26a969598baee7b97f0a1a4cd419c9797aac65ff8f5aed2d5398faabbb9b4628696b7aed55987e8acb2b17a819cbe90bb508592acd2d946a1ade5b0c27c94bdf8b0d26ef6e9ad87ce65b391a2b8e44b92f5fdaee56fb096ac8cd837e2a3; gdxidpyhxdE=V9Pnc%5CLTb2HTGVfNr3WCQW%2FW%5CCPZOK0XnGxAObaD1RC8G7lwZLG5UoGtylAs8UsXb6no7b1wQyxAg%2FPw1xBoaf%2BUulfG9%2BxkiQ0JwMdYZodeVJ6V1lpw1YfGScmJnPoMVyVrarJ%5CzZja6%2FMUjsb4IjY1wB2gyVrmaOoSKxvHeJfUiKGZ%3A1647680377803; captcha_session_v2=2|1:0|10:1647679931|18:captcha_session_v2|88:RlMvRkdSRUU4dCtjMnlPRmVnN2JjQ2EzeCtJT3p1YkViREM2L3R1TlByMXdna0RRRWpiSWVwMUd6cWFxa2dKdQ==|b723dce9ca2e90df4737d71cbe58ceb6006083307013e7b44c5557d89ec4ff2f; captcha_ticket_v2=2|1:0|10:1647679942|17:captcha_ticket_v2|704:eyJ2YWxpZGF0ZSI6IkNOMzFfUHZ1dGhpeHdVa3VCcGM3S3Z4MDBwcGVDd2ZLV0dLVWtCQVRpZlFuUGNQZi15U3hZMnJQMkQxYlE0ZTlYSzlrTTdzTnVuaXZySjZyb3RYcnlwa0hUODhVbFFzU1poT0NIR0FVYU56YlJPc3lEYzlGbldQbFp2VGRVTXBMX3J4djVVbUhtd1VWRGI4MFB1QUhyVUxWUzhDVGpYN1lHaHBYRDBJQndwcVZUZDFsUWVqTXFWUHE0MnFicGhGSEFQZFVsb0dXd1FYd3g3X21VekdUd0ZUWllhNUtoam9vT1NwMHQxUUxzWEZXZmN2d0N0Y3NLamxvZ244LTRxd2t2Zk41WVZpc3dlOVJKUkwyZ21vNWFBcy03N0ZqbXFBZXNiRlpiRklIeC5ndnRkSUJlUi5CR2hQR05VQnMxWGRHUFh3aG11TGlrdEVhbnNoR1dzN2RLZXVzWks1eG1zMXVSeXdqaGR4VW1GeEVIc1VNZ2xQMXU1NnNyVmxCWUdTSmFESlJHNU9zUnlDVG1mTHE3OW92MFdiV1lYQzRmdXFXOVVaMnlHLjRKWEFNQTZVeHh6cXBGcExjLWtWd1FKMG9UbG03OFVyYmlRMmFJeVg4emhJVkd4WU5uRnRmNmo4NFdjZG54SS5WQ1F6LXhjRWNuS21hNi1CVVJTcHVULXNwMyJ9|deb333726cc56b3900502d1c8ecd3dd1d91e56ba4090ee683a0af86c18986274; z_c0=2|1:0|10:1647679962|4:z_c0|92:Mi4xaGlpOEJBQUFBQUFBZ042WGhGQW5GQ1lBQUFCZ0FsVk4ydWNpWXdCUVpFSXBYM2JBWTdMSnFEUHZ6VnhIT01HZXd3|63b889aa3a6ee8c58b1e84d9a7de39bfa84a8b8c74f14775399c623f0d73f11a; q_c1=87cdf895ba3444b8bdf4fc2bf51766c2|1647679962000|1647679962000; NOT_UNREGISTER_WAITING=1; tst=h; '
}
hdr = request.Request(url, headers=headers)
resp = request.urlopen(hdr)
print(resp.read().decode('utf8'))
http.cookiejar模組
介於每次處理cookie比較麻煩還得從瀏覽器上copy過來使用,現在使用cookiejar模組來進行優化;
該模組主要的類有CookieJar、FileCookieJar、MozillaCookieJar、LWPCookieJar。這四個類的作用分別如下:
- CookieJar:管理HTTP cookie值、儲存HTTP請求生成的cookie、向傳出的HTTP請求新增cookie的物件。整個cookie都儲存在記憶體中,對CookieJar例項進行垃圾回收後cookie也將丟失;
- FileCookieJar (filename,delayload=None,policy=None):從CookieJar派生而來,用來建立FileCookieJar例項,檢索cookie資訊並將cookie儲存到檔案中。filename是儲存cookie的檔名。delayload為True時支援延遲訪問訪問檔案,即只有在需要時才讀取檔案或在檔案中儲存資料;
- MozillaCookieJar (filename,delayload=None,policy=None):從FileCookieJar派生而來,建立與Mozilla瀏覽器 cookies.txt相容的FileCookieJar例項;
- LWPCookieJar (filename,delayload=None,policy=None):從FileCookieJar派生而來,建立與libwww-perl標準的 Set-Cookie3 檔案格式相容的FileCookieJar例項;
from urllib import request
from urllib import parse
from http.cookiejar import CookieJar
# 登入:https://i.meishi.cc/login.php?redirect=https%3A%2F%2Fwww.meishij.net%2F
#個人網頁https://i.meishi.cc/cook.php?id=13686422
headers={
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
}
#1.登入
#1.1 建立cookiejar物件
cookiejar = CookieJar()
#1.2 使用cookiejar建立一個HTTPCookieProcess物件
handler = request.HTTPCookieProcessor(cookiejar)
#1.3 使用上一步的建立的handler建立一個opener
opener = request.build_opener(handler)
#1.4 使用opener傳送登入請求 (賬號和密碼)
post_url = 'https://i.meishi.cc/login.php?redirect=https%3A%2F%2Fwww.meishij.net%2F'
post_data = parse.urlencode({
'username':'1097566154@qq.com',
'password':'wq15290884759.'
})
'''如果登入後傳送的是GET請求,那麼就不需要攜帶登入資訊直接獲取'''
req = request.Request(post_url,data=post_data.encode('utf-8'))
opener.open(req)
#2.訪問個人網頁
url = 'https://i.meishi.cc/cook.php?id=13686422'
rq = request.Request(url,headers=headers)
resp = opener.open(rq)
print(resp.read().decode('utf-8'))
Cookie載入與儲存
from urllib import request
from http.cookiejar import MozillaCookieJar
# 儲存
cookiejar = MozillaCookieJar('cookie.txt')
handler = request.HTTPCookieProcessor(cookiejar)
opener = request.build_opener(handler)
resp = opener.open('https://www.baidu.com')
# resp = opener.open('http://www.httpbin.org/cookies/set/course/abc')
cookiejar.save(ignore_discard=True,ignore_expires=True)
# ignore_discard=True 即使cookies即將被丟棄也要儲存下來
# ignore_expires=True 如果cookies已經過期也將它儲存並且檔案已存在時將覆蓋
from urllib import request
from http.cookiejar import MozillaCookieJar
#載入
cookiejar = MozillaCookieJar('cookie.txt')
cookiejar.load()
for cookie in cookiejar:
print(cookie)