第一部分:前情摘要
一、web請求全過程剖析
我們?yōu)g覽器在輸入完網址到我們看到網頁的整體內容, 這個過程中究竟發(fā)生了些什么?
我們看一下一個瀏覽器請求的全過程
接下來就是一個比較重要的事情了. 所有的數據都在頁面源代碼里么? 非也~ 這里要介紹一個新的概念
那就是頁面渲染數據的過程, 我們常見的頁面渲染過程有兩種,
-
服務器渲染, 你需要的數據直接在頁面源代碼里能搜到
這個最容易理解, 也是最簡單的. 含義呢就是我們在請求到服務器的時候, 服務器直接把數據全部寫入到html中, 我們?yōu)g覽器就能直接拿到帶有數據的html內容. 比如,
由于數據是直接寫在html中的, 所以我們能看到的數據都在頁面源代碼中能找的到的.
這種網頁一般都相對比較容易就能抓取到頁面內容.
-
前端JS渲染, 你需要的數據在頁面源代碼里搜不到
這種就稍顯麻煩了. 這種機制一般是第一次請求服務器返回一堆HTML框架結構. 然后再次請求到真正保存數據的服務器, 由這個服務器返回數據, 最后在瀏覽器上對數據進行加載. 就像這樣:
js渲染代碼(示例)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>案例:動態(tài)渲染頁面</title> <style> table{ width: 300px; text-align: center; } </style> </head> <body> <table border="1" cellspacing="0"> <thead> <tr> <th>ID</th> <th>姓名</th> <th>年齡</th> </tr> </thead> <tbody> <!-- js渲染--> </tbody> </table> <script> //提前準備好的數據 var users = [ {id: 1, name: '張三', age: 18}, {id: 2, name: '李四', age: 28}, {id: 3, name: '王麻子', age: 38} ] //獲取tbody標簽 var tbody = document.querySelector('tbody') //1.循環(huán)遍歷users數據 users.forEach(function (item) { //這里的item 就是數組中的每一個對象 console.log(item) //2. 每一個對象生成一個tr標簽 var tr = document.createElement('tr') //循環(huán)遍歷item for(var key in item){ //生成td標簽 var td = document.createElement('td') td.innerHTML = item[key] //5.把td 插入到tr內部 tr.appendChild(td) } //把本次的tr插入到tbody的內部 tbody.appendChild(tr) }) </script> </body> </html>
這樣做的好處是服務器那邊能緩解壓力. 而且分工明確. 比較容易維護. 典型的有這么一個網頁
那數據是何時加載進來的呢? 其實就是在我們進行頁面向下滾動的時候, jd就在偷偷的加載數據了, 此時想要看到這個頁面的加載全過程, 我們就需要借助瀏覽器的調試工具了(F12)
看到了吧, 頁面上看到的內容其實是后加載進來的.
OK, 在這里我不是要跟各位講jd有多牛B, 也不是說這兩種方式有什么不同, 只是想告訴各位, 有些時候, 我們的數據不一定都是直接來自于頁面源代碼. 如果你在頁面源代碼中找不到你要的數據時, 那很可能數據是存放在另一個請求里.
1.你要的東西在頁面源代碼. 直接拿`源代碼`提取數據即可
2.你要的東西,不在頁面源代碼, 需要想辦法找到真正的加載數據的那個請求. 然后提取數據
二、瀏覽器工具的使用
Chrome是一款非常優(yōu)秀的瀏覽器. 不僅僅體現在用戶使用上. 對于我們開發(fā)人員而言也是非常非常好用的.
對于一名爬蟲工程師而言. 瀏覽器是最能直觀的看到網頁情況以及網頁加載內容的地方. 我們可以按下F12來查看一些普通用戶很少能使用到的工具.
其中, 最重要的Elements, Console, Sources, Network.
Elements是我們實時的網頁內容情況, 注意, 很多兄弟尤其到了后期. 非常容易混淆Elements以及頁面源代碼之間的關系.
注意,
- 頁面源代碼是執(zhí)行js腳本以及用戶操作之前的服務器返回給我們最原始的內容
- Elements中看到的內容是js腳本以及用戶操作之后的當時的頁面顯示效果.
你可以理解為, 一個是老師批改之前的卷子, 一個是老師批改之后的卷子. 雖然都是卷子. 但是內容是不一樣的. 而我們目前能夠拿到的都是頁面源代碼. 也就是老師批改之前的樣子. 這一點要格外注意.
在Elements中我們可以使用左上角的小箭頭.可以直觀的看到瀏覽器中每一塊位置對應的當前html狀況. 還是很貼心的.
第二個窗口, Console是用來查看程序員留下的一些打印內容, 以及日志內容的. 我們可以在這里輸入一些js代碼自動執(zhí)行.
等咱們后面講解js逆向的時候會用到這里.
第三個窗口, Source, 這里能看到該網頁打開時加載的所有內容. 包括頁面源代碼. 腳本. 樣式, 圖片等等全部內容.
第四個窗口, Network, 我們一般習慣稱呼它為抓包工具. 在這里, 我們能看到當前網頁加載的所有網路網絡請求, 以及請求的詳細內容. 這一點對我們爬蟲來說至關重要.
其他更加具體的內容. 隨著咱們學習的展開. 會逐一進行講解.
三、反爬蟲的一般手段
爬蟲項目最復雜的不是頁面信息的提取,反而是爬蟲與反爬蟲、反反爬蟲的博弈過程
-
User-Agent
瀏覽器的標志信息,會通過請求頭傳遞給服務器,用以說明訪問數據的瀏覽器信息
反爬蟲:先檢查是否有UA,或者UA是否合法
-
代理IP
-
驗證碼訪問
-
動態(tài)加載網頁
-
數據加密
-
…
四、常見HTTP狀態(tài)碼
-
200:這個是最常見的http狀態(tài)碼,表示服務器已經成功接受請求,并將返回客戶端所請
-
100-199 用于指定客戶端應相應的某些動作。
-
200-299 用于表示請求成功。
-
300-399 用于已經移動的文件并且常被包含在定位頭信息中指定新的地址信息。
-
400-499 用于指出客戶端的錯誤。
- 404:請求失敗,客戶端請求的資源沒有找到或者是不存在
-
500-599 服務器遇到未知的錯誤,導致無法完成客戶端當前的請求。
第二部分:urllib與requests
一、urllib的學習
學習目標
了解urllib的基本使用
一、urllib介紹
除了requests模塊可以發(fā)送請求之外, urllib模塊也可以實現請求的發(fā)送,只是操作方法略有不同!
urllib在python中分為urllib和urllib2,在python3中為urllib
下面以python3的urllib為例進行講解
二、urllib的基本方法介紹
2.1 urllib.Request
-
構造簡單請求
import urllib #構造請求 request = urllib.request.Request("http://www.baidu.com") #發(fā)送請求獲取響應 response = urllib.request.urlopen(request)
-
傳入headers參數
import urllib #構造headers headers = {"User-Agent" : "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"} #構造請求 request = urllib.request.Request(url, headers = headers) #發(fā)送請求 response = urllib.request.urlopen(request)
-
傳入data參數 實現發(fā)送post請求(示例)
import urllib.request import urllib.parse import json url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword' headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15', } data = { 'cname': '', 'pid': '', 'keyword': '北京', 'pageIndex': 1, 'pageSize': 10, } # 使用post方式 # 需要 data = urllib.parse.urlencode(data).encode('utf-8') req = urllib.request.Request(url, data=data, headers=headers) res = urllib.request.urlopen(req) print(res.getcode()) print(res.geturl()) data = json.loads(res.read().decode('utf-8')) # print(data) for i in data['Table1']: print(i)
2.2 response.read()
獲取響應的html字符串,bytes類型
#發(fā)送請求
response = urllib.request.urlopen("http://www.baidu.com")
#獲取響應
response.read()
三、urllib請求百度首頁的完整例子
import urllib
import json
url = 'http://www.baidu.com'
#構造headers
headers = {"User-Agent" : "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"}
#構造請求
request = urllib.request.Request(url, headers = headers)
#發(fā)送請求
response = urllib.request.urlopen(request)
#獲取html字符串
html_str = response.read().decode('utf-8')
print(html_str)
四、小結
- urllib.request中實現了構造請求和發(fā)送請求的方法
- urllib.request.Request(url,headers,data)能夠構造請求
- urllib.request.urlopen能夠接受request請求或者url地址發(fā)送請求,獲取響應
- response.read()能夠實現獲取響應中的bytes字符串
第三部分:requests模塊的入門使用
一、requests模塊的入門使用
學習目標:
- 了解 requests模塊的介紹
- 掌握 requests的基本使用
- 掌握 response常見的屬性
- 掌握 requests.text和content的區(qū)別
- 掌握 解決網頁的解碼問題
- 掌握 requests模塊發(fā)送帶headers的請求
- 掌握 requests模塊發(fā)送帶參數的get請求
1、為什么要重點學習requests模塊,而不是urllib
- 企業(yè)中用的最多的就是requests
- requests的底層實現就是urllib
- requests在python2 和python3中通用,方法完全一樣
- requests簡單易用
2、requests的作用與安裝
作用:發(fā)送網絡請求,返回響應數據
安裝:pip install requests
3、requests模塊發(fā)送簡單的get請求、獲取響應
需求:通過requests向百度首頁發(fā)送請求,獲取百度首頁的數據
import requests
# 目標url
url = 'https://www.baidu.com'
# 向目標url發(fā)送get請求
response = requests.get(url)
# 打印響應內容
print(response.text)
response的常用屬性:
-
response.text
響應體 str類型 -
response.encoding
從HTTP header中猜測的響應內容的編碼方式 -
respones.content
響應體 bytes類型 -
response.status_code
響應狀態(tài)碼 -
response.request.headers
響應對應的請求頭 -
response.headers
響應頭 -
response.request.cookies
響應對應請求的cookie -
response.cookies
響應的cookie(經過了set-cookie動作) -
response.url
獲取訪問的url -
response.json()
獲取json數據 得到內容為字典 (如果接口響應體的格式是json格式時) -
response.ok
如果status_code小于200,response.ok返回True。
如果status_code大于200,response.ok返回False。
思考:text是response的屬性還是方法呢?
- 一般來說名詞,往往都是對象的屬性,對應的動詞是對象的方法
3.1 response.text 和response.content的區(qū)別
-
response.text
- 類型:str
- 解碼類型: requests模塊自動根據HTTP 頭部對響應的編碼作出有根據的推測,推測的文本編碼
- 如何修改編碼方式:
response.encoding="gbk/UTF-8"
-
response.content
- 類型:bytes
- 解碼類型: 沒有指定
- 如何修改編碼方式:
response.content.deocde("utf8")
獲取網頁源碼的通用方式:
response.content.decode()
response.content.decode("UTF-8")
response.text
以上三種方法從前往后嘗試,能夠100%的解決所有網頁解碼的問題
所以:更推薦使用response.content.deocde()
的方式獲取響應的html頁面
3.2 練習:把網絡上的圖片保存到本地
我們來把
www.baidu.com
的圖片保存到本地
思考:
- 以什么方式打開文件
- 保存什么格式的內容
分析:
- 圖片的url: https://www.baidu.com/img/bd_logo1.png
- 利用requests模塊發(fā)送請求獲取響應
- 以2進制寫入的方式打開文件,并將response響應的二進制內容寫入
import requests
# 圖片的url
url = 'https://www.baidu.com/img/bd_logo1.png'
# 響應本身就是一個圖片,并且是二進制類型
response = requests.get(url)
# print(response.content)
# 以二進制+寫入的方式打開文件
with open('baidu.png', 'wb') as f:
# 寫入response.content bytes二進制類型
f.write(response.content)
4、發(fā)送帶header的請求
我們先寫一個獲取百度首頁的代碼
import requests
url = 'https://www.baidu.com'
response = requests.get(url)
print(response.content)
# 打印響應對應請求的請求頭信息
print(response.request.headers)
4.1 思考
對比瀏覽器上百度首頁的網頁源碼和代碼中的百度首頁的源碼,有什么不同?
代碼中的百度首頁的源碼非常少,為什么?
4.2 為什么請求需要帶上header?
模擬瀏覽器,欺騙服務器,獲取和瀏覽器一致的內容
4.3 header的形式:字典
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"}
4.4 用法
requests.get(url, headers=headers)
4.5 完整的代碼
import requests
url = 'https://www.baidu.com'
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"}
# 在請求頭中帶上User-Agent,模擬瀏覽器發(fā)送請求
response = requests.get(url, headers=headers)
# print(response.content)
# 打印請求頭信息
print(response.request.headers)
5、發(fā)送帶參數的請求
我們在使用百度搜索的時候經常發(fā)現url地址中會有一個
?
,那么該問號后邊的就是請求參數,又叫做查詢字符串
5.1 什么叫做請求參數:
例1: http://www.webkaka.com/tutorial/server/2015/021013/
例2:https://www.baidu.com/s?wd=python&a=c
例1中沒有請求參數!例2中?后邊的就是請求參數
5.2 請求參數的形式:字典
kw = {'wd':'長城'}
5.3 請求參數的用法
requests.get(url,params=kw)
5.4 關于參數的注意點
在url地址中, 很多參數是沒有用的,比如百度搜索的url地址,其中參數只有一個字段有用,其他的都可以刪除 如何確定那些請求參數有用或者沒用:挨個嘗試! 對應的,在后續(xù)的爬蟲中,越到很多參數的url地址,都可以嘗試刪除參數
5.5 兩種方式:發(fā)送帶參數的請求
-
對
https://www.baidu.com/s?wd=python
發(fā)起請求可以使用requests.get(url, params=kw)
的方式# 方式一:利用params參數發(fā)送帶參數的請求 import requests 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"} # 這是目標url # url = 'https://www.baidu.com/s?wd=python' # 最后有沒有問號結果都一樣 url = 'https://www.baidu.com/s?' # 請求參數是一個字典 即wd=python kw = {'wd': 'python'} # 帶上請求參數發(fā)起請求,獲取響應 response = requests.get(url, headers=headers, params=kw) # 當有多個請求參數時,requests接收的params參數為多個鍵值對的字典,比如 '?wd=python&a=c'-->{'wd': 'python', 'a': 'c'} print(response.content)
-
也可以直接對
https://www.baidu.com/s?wd=python
完整的url直接發(fā)送請求,不使用params參數# 方式二:直接發(fā)送帶參數的url的請求 import requests 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"} url = 'https://www.baidu.com/s?wd=python' # kw = {'wd': 'python'} # url中包含了請求參數,所以此時無需params response = requests.get(url, headers=headers)
6、練習
1.獲取新浪首頁,查看response.text 和response.content.decode()的區(qū)別
2.實現任意貼吧的爬蟲,保存網頁到本地
7、小結
- requests模塊的介紹:能夠幫助我們發(fā)起請求獲取響應
- requests的基本使用:
requests.get(url)
- 以及response常見的屬性:
-
response.text
響應體 str類型 -
respones.content
響應體 bytes類型 -
response.status_code
響應狀態(tài)碼 -
response.request.headers
響應對應的請求頭 -
response.headers
響應頭 -
response.request._cookies
響應對應請求的cookie -
response.cookies
響應的cookie(經過了set-cookie動作)
-
- 掌握 requests.text和content的區(qū)別:text返回str類型,content返回bytes類型
- 掌握 解決網頁的解碼問題:
response.content.decode()
response.content.decode("UTF-8")
response.text
- 掌握 requests模塊發(fā)送帶headers的請求:
requests.get(url, headers={})
- 掌握 requests模塊發(fā)送帶參數的get請求:
requests.get(url, params={})
二、requests模塊的深入使用
學習目標:
- 能夠應用requests發(fā)送post請求的方法
- 能夠應用requests模塊使用代理的方法
- 了解代理ip的分類
1、使用requests發(fā)送POST請求
思考:哪些地方我們會用到POST請求?
- 登錄注冊( POST 比 GET 更安全)
- 需要傳輸大文本內容的時候( POST 請求對數據長度沒有要求)
所以同樣的,我們的爬蟲也需要在這兩個地方回去模擬瀏覽器發(fā)送post請求
1.1 requests發(fā)送post請求語法:
-
用法:
response = requests.post("http://www.baidu.com/", data = data, headers=headers)
-
data 的形式:字典
1.2 POST請求練習
下面面我們通過金山翻譯的例子看看post請求如何使用:
地址:https://www.iciba.com/fy
思路分析
-
抓包確定請求的url地址
-
確定請求的參數
-
確定返回數據的位置
-
模擬瀏覽器獲取數據
import requests import json 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"} url = 'https://ifanyi.iciba.com/index.php?c=trans&m=fy&client=6&auth_user=key_ciba&sign=99730f3bf66b2582' data = { 'from': 'zh', 'to': 'en', 'q': 'lucky 是一個帥氣的老師' } res = requests.post(url, headers=headers, data=data) # print(res.status_code) # 返回的是json字符串 需要在進行轉換為字典 data = json.loads(res.content.decode('UTF-8')) # print(type(data)) print(data) print(data['content']['out'])
1.3 小結
在模擬登陸等場景,經常需要發(fā)送post請求,直接使用requests.post(url,data)
即可
2、使用代理
2.1 為什么要使用代理
- 讓服務器以為不是同一個客戶端在請求
- 防止我們的真實地址被泄露,防止被追究
2.2 理解使用代理的過程
2.3 理解正向代理和反向代理的區(qū)別
通過上圖可以看出:
- 正向代理:對于瀏覽器知道服務器的真實地址,例如VPN
- 反向代理:瀏覽器不知道服務器的真實地址,例如nginx
詳細講解:
正向代理是客戶端與正向代理客戶端在同一局域網,客戶端發(fā)出請求,正向代理 替代客戶端向服務器發(fā)出請求。服務器不知道誰是真正的客戶端,正向代理隱藏了真實的請求客戶端。
反向代理:服務器與反向代理在同一個局域網,客服端發(fā)出請求,反向代理接收請求 ,反向代理服務器會把我們的請求分轉發(fā)到真實提供服務的各臺服務器Nginx就是性能非常好的反向代理服務器,用來做負載均衡
2.4 代理的使用
-
用法:
requests.get("http://www.baidu.com", proxies = proxies)
-
proxies的形式:字典
-
例如:
proxies = { "http": "http://12.34.56.79:9527", "https": "https://12.34.56.79:9527", }
2.5 代理IP的分類
根據代理ip的匿名程度,代理IP可以分為下面四類:
- 透明代理(Transparent Proxy):透明代理的意思是客戶端根本不需要知道有代理服務器的存在,但是它傳送的仍然是真實的IP。使用透明代理時,對方服務器是可以知道你使用了代理的,并且他們也知道你的真實IP。你要想隱藏的話,不要用這個。透明代理為什么無法隱藏身份呢?因為他們將你的真實IP發(fā)送給了對方服務器,所以無法達到保護真實信息。
- 匿名代理(Anonymous Proxy):匿名代理隱藏了您的真實IP,但是向訪問對象可以檢測是使用代理服務器訪問他們的。會改變我們的請求信息,服務器端有可能會認為我們使用了代理。不過使用此種代理時,雖然被訪問的網站不能知道你的ip地址,但仍然可以知道你在使用代理,當然某些能夠偵測ip的網頁也是可以查到你的ip。(https://wenku.baidu.com/view/9bf7b5bd3a3567ec102de2bd960590c69fc3d8cf.html)
- 高匿代理(Elite proxy或High Anonymity Proxy):高匿名代理不改變客戶機的請求,這樣在服務器看來就像有個真正的客戶瀏覽器在訪問它,這時客戶的真實IP是隱藏的,完全用代理服務器的信息替代了您的所有信息,就象您就是完全使用那臺代理服務器直接訪問對象,同時服務器端不會認為我們使用了代理。IPDIEA覆蓋全球240+國家地區(qū)ip高匿名代理不必擔心被追蹤。
在使用的使用,毫無疑問使用高匿代理效果最好
從請求使用的協議可以分為:
- http代理
- https代理
- socket代理等
不同分類的代理,在使用的時候需要根據抓取網站的協議來選擇
2.6 代理IP使用的注意點
-
反反爬
使用代理ip是非常必要的一種
反反爬
的方式但是即使使用了代理ip,對方服務器任然會有很多的方式來檢測我們是否是一個爬蟲,比如:
-
一段時間內,檢測IP訪問的頻率,訪問太多頻繁會屏蔽
-
檢查Cookie,User-Agent,Referer等header參數,若沒有則屏蔽
-
服務方購買所有代理提供商,加入到反爬蟲數據庫里,若檢測是代理則屏蔽
所以更好的方式在使用代理ip的時候使用隨機的方式進行選擇使用,不要每次都用一個代理ip
-
-
代理ip池的更新
購買的代理ip很多時候大部分(超過60%)可能都沒辦法使用,這個時候就需要通過程序去檢測哪些可用,把不能用的刪除掉。
-
代理服務器平臺的使用:
當然還有很多免費的,但是大多都不可用需要自己嘗試
- http://www.66ip.cn
- https://ip.jiangxianli.com/?page=1
- https://www.zdaye.com
- https://www.kuaidaili.com/free
3、配置
-
瀏覽器配置代理
右邊三點==> 設置==> 高級==> 代理==> 局域網設置==> 為LAN使用代理==> 輸入ip和端口號即可
參考網址:https://jingyan.baidu.com/article/a681b0dece76407a1843468d.html
-
代碼配置
urllib文章來源:http://www.zghlxwxcb.cn/news/detail-857565.html
handler = urllib.request.ProxyHandler({'http': '114.215.95.188:3128'}) opener = urllib.request.build_opener(handler) # 后續(xù)都使用opener.open方法去發(fā)送請求即可
requests文章來源地址http://www.zghlxwxcb.cn/news/detail-857565.html
# 用到的庫 import requests # 寫入獲取到的ip地址到proxy # 一個ip地址 proxy = { "http": "http://12.34.56.79:9527", "https": "https://12.34.56.79:9527", } """ # 多個ip地址 proxy = [ {'https':'https://221.178.232.130:8080'}, {'http':'http://221.178.232.130:8080'} ] import random proxy = random.choice(proxy) """ # 用百度檢測ip代理是否成功 url = 'https://www.baidu.com/s?' # 請求網頁傳的參數 params={ 'wd':'ip地址' } # 請求頭 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36' } # 發(fā)送get請求 response = requests.get(url=url,headers=headers,params=params,proxies=proxy) # 獲取返回頁面保存到本地,便于查看 with open('ip.html','w',encoding='utf-8') as f: f.write(response.text)
4、小結
- requests發(fā)送post請求使用requests.post方法,帶上請求體,其中請求體需要時字典的形式,傳遞給data參數接收
- 在requests中使用代理,需要準備字典形式的代理,傳遞給proxies參數接收
- 不同協議的url地址,需要使用不同的代理去請求
到了這里,關于5.爬蟲必備基礎知識(urllib&requests)一的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!