爬蟲中網路請求的那些事之urllib庫

HammerZe發表於2022-03-19

爬蟲之網路請求中的那些事

image

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.
    '''

image

引數詳解

  • 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')

image

部分原始碼

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.
    """

image
image

格式

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')

image

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

image

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問題,原理是通過請求代理伺服器,然後通過代理伺服器請求目標伺服器,然後返回資料

image

常用的代理有:

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?

指某些網站為了辨別使用者身份、進行 session 跟蹤而儲存在使用者本地終端上的資料,cookie儲存的資料量有限,不同的瀏覽器有不同的儲存大小,但一般不超過4KB。因此使用cookie只能儲存一些小量的資料;

?Cookie、Session

我們知道HTTP的四大特徵:

  • 基於請求響應:服務端永遠不會主動給客戶端發訊息 必須是客戶端先發請求,如果想讓服務端主動給客戶端傳送訊息可以採用其他網路協議;
  • 基於TCP/UDP、IP作用於應用層之上的協議
  • 無狀態:不儲存客戶端的狀態資訊,不知道是屬於哪一個使用者發過來的請求,每次過來都彷彿第一次看見(待你如初戀)
  • 無連線/短連線:兩者請求響應之後立刻斷絕關係

image

image

  • 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協議下起作用。

image

實戰:爬蟲使用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)

相關文章