爬蟲-1.概述和HTTP請求與響應處理
概述
- 爬蟲,應該稱爲網絡爬蟲,也叫網頁蜘蛛、網絡機器人、網絡螞蟻等。
- 搜索引擎,就是網絡爬蟲的應用者。
- 大數據時代的到來,所有企業都希望通過海量數據發現其中的價值。所以需要爬取對特定網站、特頂類別的數據,而搜索引擎不能提供這樣的功能,因此需要自己開發爬蟲來解決。
爬蟲分類
1.通用爬蟲
常見就是搜索引擎,無差別的蒐集數據、存儲、提取關鍵字、構建索引庫,給用戶提供搜索接口。
- 爬取一般流程
- 初始化一批URL,將這些URL放到帶爬隊列
- 從隊列取出這些URL,通過DNS解析IP,對IP對應的站點下載HTML頁面,保存到本地服務器中,爬取完的URL放到已爬取隊列。
- 分析這些網頁內容,找出網頁裏面的其他關心的URL鏈接,繼續執行第2步,直到爬取條件結束。
- 搜索引擎如何獲取一個網站的URL
- 新網站主動提交給搜索引擎
- 通過其他網站頁面中設置的外鏈接
- 搜索引擎和DNS服務商合作,獲取最新收錄的網站
2. 聚焦爬蟲
- 有針對性的編寫特定領域數據的爬取程序,針對 某些類別數據採集的爬蟲,是面向主題的爬蟲
Robots協議
指定一個robots.txt文件,告訴爬蟲引擎什麼可以爬取
/
表示網站根目錄,表示網站所有目錄。Allow
允許爬取的目錄Disallow
禁止爬取的目錄- 可以使用通配符
robots是一個君子協定,“爬亦有道”
這個協議爲了讓搜索引擎更有效率搜索自己內容,提供了Sitemap這樣的文件。Sitemap往往是一個XML文件,提供了網站想讓大家爬取的內容的更新信息。
這個文件禁止爬取的往往又是可能我們感興趣的內容,反而泄露了這些地址。
-
示例:淘寶的robotshttp://www.taobao.com/robots.txt
User-agent: Baiduspider Allow: /article Allow: /oshtml Allow: /ershou Allow: /$ Disallow: /product/ Disallow: / User-Agent: Googlebot Allow: /article Allow: /oshtml Allow: /product Allow: /spu Allow: /dianpu Allow: /oversea Allow: /list Allow: /ershou Allow: /$ Disallow: / User-agent: Bingbot Allow: /article Allow: /oshtml Allow: /product Allow: /spu Allow: /dianpu Allow: /oversea Allow: /list Allow: /ershou Allow: /$ Disallow: / User-Agent: 360Spider Allow: /article Allow: /oshtml Allow: /ershou Disallow: / User-Agent: Yisouspider Allow: /article Allow: /oshtml Allow: /ershou Disallow: / User-Agent: Sogouspider Allow: /article Allow: /oshtml Allow: /product Allow: /ershou Disallow: / User-Agent: Yahoo! Slurp Allow: /product Allow: /spu Allow: /dianpu Allow: /oversea Allow: /list Allow: /ershou Allow: /$ Disallow: / User-Agent: * Disallow: /
-
示例馬蜂窩tobotshttp://www.mafengwo.cn/robots.txt
User-agent: * Disallow: / Disallow: /poi/detail.php Sitemap: http://www.mafengwo.cn/sitemapIndex.xml
HTTP請求和響應處理
其實爬取網頁就是通過HTTP協議訪問網頁,不過通過瀏覽器反問往往是人的行爲,把這種行爲變成使用程序來訪問。
urllib包
urllib是標準庫,它一個工具包模塊,包含下面模塊來處理url:
* urllib.request 用於打開和讀寫url
* urllib.error 包含了由urllib.request引起的異常
* urllib.parse 用於解析url
* urllib.robotparser 分析robots.txt文件
Python2中提供了urllib和urllib2。urllib提供較爲底層的接口,urllib2對urllib進行了進一步封裝。Python3中將urllib合併到了urllib2中,並更名爲標準庫urllib包。
urllib.request模塊
定義了在基本和摘要式身份驗證、重定向、cookies等應用中打開Url(主要是HTTP)的函數和類。
-
urlopen方法
urlopen(url,data=None)
- url是鏈接地址字符串,或請求類的實例
- data提交的數據,如果data爲Non發起的GET請求,否則發起POST請求。見
urllib.request.Request#get_method
返回http.client.HTTPResponse類的相遇對象,這是一個類文件對象。
from urllib.request import urlopen # 打開一個url返回一個相應對象,類文件對象 # 下面鏈接訪問後會有跳轉 responses = urlopen("http://www.bing.com") #默認GET方法 print(responses.closed) with responses: print(1, type(responses)) # http.client.HTTPResponse類文件對象 print(2,responses.status,responses.reason) #狀態 print(3,responses.geturl()) #返回真正的URL print(4,responses.info()) #headers print(5,responses.read()[:50]) #讀取返回的內容 print(responses.closed)
1. 上例,通過urllib.request.urlopen方法,發起一個HTTP的GET請求,WEB服務器返回了網頁內容。響應的數據被封裝到類文件對象中,可以通過read方法、readline方法、readlines方法獲取數據,status和reason屬性表示返回的狀態碼,info方法返回頭信息,等等。
-
User-Agent問題
-
上例代碼非常精簡,即可以獲得網站的響應數據。但目前urlopen方法通過url字符串和data發起HTTP的請求。如果想修改HTTP頭,例如useragent,就的藉助其他方式。
- 原碼中構造的useragen如下:
# urllib.request.OpenerDirector class OpenerDirector: def __init__(self): client_version = "Python-urllib/%s" % __version__ self.addheaders = [('User-agent', client_version)] ``` * 當前顯示爲Python-urlib/3.7 * 有些網站是反爬蟲的,所以要把爬蟲僞裝成瀏覽器。順便打開一個瀏覽器,複製李立羣的UA值,用來僞裝。
-
-
Request類
Request(url,data=None,headers={})
初始化方法,構造一個請求對象。可添加一個header的字典。data參數決定是GET還是POST請求。
obj.add_header(key,val)
爲header增加一個鍵值對。
from urllib.request import Request,urlopen
import random
# 打開一個url返回一個Request請求對象
# url = "https://movie.douban.com/" #注意尾部的斜槓一定要有
url = "http://www.bing.com/"
ua_list = [
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", # chrome
"Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN) AppleWebKit/537.36 (KHTML, like Gecko) Version/5.0.1 Safari/537.36", # safafi
"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:50.0) Gecko/20100101 Firefox/50.0", # Firefox
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)" # IE
]
ua = random.choice(ua_list)
request = Request(url)
request.add_header("User-Agent",ua)
print(type(request))
response = urlopen(request,timeout=20) #request對象或者url都可以
print(type(response))
with response:
print(1,response.status,response.getcode(),response.reason) #狀態,getcode本質上就是返回status
print(2,response.geturl()) #返回數據的url。如果重定向,這個url和原始url不一樣
# 例如:原始url是http://www.bing.com/,返回http://cn.bing.com/
print(3,response.info()) #返回響應頭headers
print(4,response.read()[:50]) #讀取返回的內容
print(5,request.get_header("User-agent"))
print(6,request.headers)
print(7,"user-agent".capitalize())
urllib.parse模塊
該模塊可以完成對url的編解碼
- parse.urlencode({key:value}) #對查詢字符串進行編碼
from urllib import parse
u = parse.urlencode({
"url":"http://www.xdd.com/python",
"p_url":"http://www.xdd.com/python?id=1&name=張三"
})
print(u)
# 運行結果
url=http%3A%2F%2Fwww.xdd.com%2Fpython&p_url=http%3A%2F%2Fwww.xdd.com%2Fpython%3Fid%3D1%26name%3D%E5%BC%A0%E4%B8%89
從運行結果來看冒號、斜槓、&、等號、問號等符號全部被編碼了,%之後實際上是單字節十六進制表示的值。
一般來說url中的地址部分,一般不需要使用中文路徑,但是參數部分,不管GET還是POST方法,提交的數據中, 可能有斜杆、等號、問號等符號,這樣這些字符表示數據,不表示元字符。如果直接發給服務器端,就會導致接收 方無法判斷誰是元字符,誰是數據了。爲了安全,一般會將數據部分的字符做url編碼,這樣就不會有歧義了。 後來可以傳送中文,同樣會做編碼,一般先按照字符集的encoding要求轉換成字節序列,每一個字節對應的十六 進制字符串前加上百分號即可。
from urllib import parse
u = parse.urlencode({"wd":"中"}) #編碼查詢字符串
url= "https://www.baidu.com/s?{}".format(u)
print(url)
print("中".encode("utf-8")) # b'xe4\xb8\xad'
print(parse.unquote(u)) #解碼
print(parse.unquote(url))
提交方法method
- 常用的HTTP交互數據的方法是GET、POST
- GET方法,數據是通過URL傳遞的,也就是說數據是在HTTP報文的header部分。
- POST方法,數據是放在HTTP報文的body部分提交的。
- 數據是鍵值對形式,多個參數質檢使用&符號鏈接。例如a=1&b=abc
GET方法
- 鏈接
必應
搜索引擎官網,獲取一個搜索的URLhttp://cn.bing.com/search?q=神探狄仁傑
from urllib.request import urlopen,Request
from urllib.parse import urlencode
data = urlencode({"q":"神探狄仁傑"})
base_url = "http://cn.bing.com/search"
url = "{}?{}".format(base_url,data)
safafi = "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN) AppleWebKit/537.36 (KHTML, like Gecko) Version/5.0.1 Safari/537.36" # safafi
request = Request(url,headers={"User-agent":safafi})
repost = urlopen(request)
with repost:
with open("d:/abc.html","wb") as f:
f.write(repost.read())
print("ok")
POST方法
from urllib.request import Request,urlopen
from urllib.parse import urlencode
import simplejson
request = Request("http://httpbin.org/post")
request.add_header("User-agent","Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN) AppleWebKit/537.36 (KHTML, like Gecko) Version/5.0.1 Safari/537.36")
data = urlencode({"name":"張三,@=/&","age":"6"})
print(data)
res = urlopen(request,data.encode()) #POST方法,Form提價數據,如果Data的值不是None就使用Post方法,否則Get方法
with res:
j = res.read().decode() #json
print(j)
print("===============================")
print(simplejson.loads(j))
處理JSON數據
- 查看“豆瓣電影”,中的熱門電影,通過分析,我們知道這部分內容,是通過AJAX從後臺拿到的JSON數據。
- 訪問URL是
https://movie.douban.com/j/search_subjects?type=movie&tag=%E7%83%AD%E9%97%A8&page_limit=50&page_start=0
%E7%83%AD%E9%97%A8
是utf-8編碼的中文"熱門"- tag 標籤"熱門",表示熱門電影
- type 數據類型,movie是電影
- page_limit表示返回數據的總數
- page_start 表示數據偏移
- 服務器返回json數據如下:(輪播組件,共50條數據)
from urllib.request import Request,urlopen
from urllib.parse import urlencode
base_url = "https://movie.douban.com/j/search_subjects"
data = urlencode({
"tag":"熱門",
"type":"movie",
"page_limit":10,
"page_start":10
})
request = Request(base_url)
# POST方法
repost = urlopen(request,data=data.encode())
with repost:
print(repost._method)
print(repost.read().decode()[:100])
# GET方法
with urlopen("{}?{}".format(base_url,data)) as res:
print(res._method)
print(res.read().decode()[:100])
HTTPS證書忽略
-
HTTPS使用SSL安全套層協議,在傳輸層對網絡數據進行加密。HTTPS使用的時候需要證書,而證書需要CA認證。
-
CA(Certificate Authority)是數字證書認證中心的簡稱,是指發放、管理、廢除數字證書的機構。
-
CA是受信任的第三方,有CA簽發的證書具有可信任。如果用戶由於信任了CA簽發的證書導致的損失,可以追究CA的法律責任。
-
CA是層級結構,下級CA信任上級CA,且有上級CA頒發給下級CA證書並認證。
-
一些網站,例如淘寶,使用HTTPS加密數據更加安全。
-
以前舊版本12306網站需要下載證書
from urllib.request import Request,urlopen
# request = Request("http://www.12306.cn/mormhweb/") #可以訪問
# request = Request("https://www.baidu.com/") #可以訪問
request = Request("https://www.12306.cn/mormhweb/") #舊版本報SSL認證異常
request.add_header(
"User-agent",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN) AppleWebKit/537.36 (KHTML, like Gecko) Version/5.0.1 Safari/537.36"
)
# ssl.CertificateError: hostname 'www.12306.cn' doesn't match either of ......
with urlopen(request) as res:
print(res._method)
print(res.read())
- 注意:一下說明都是針對舊版本的12306網站,來講解,現在實在無法找打第二個自己給自己發證書的。
- 通過HTTPS訪問12306的時候,失敗的原因在於12306的證書未通過CA認證,它是自己生產的證書,不可信。而其它網站訪問,如
https://www.baidu.com/
並沒有提示的原因,它的證書的發行者受信任,且早就存儲在當前系統中。 - 遇到這種問題,解決思路:忽略證書不安全信息
from urllib.request import Request,urlopen
import ssl #導入ssl模塊
# request = Request("http://www.12306.cn/mormhweb/") #可以訪問
# request = Request("https://www.baidu.com/") #可以訪問
request = Request("https://www.12306.cn/mormhweb/") #舊版本報SSL認證異常
request.add_header(
"User-agent",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN) AppleWebKit/537.36 (KHTML, like Gecko) Version/5.0.1 Safari/537.36"
)
# 忽略不信任的證書
context = ssl._create_unverified_context()
res = urlopen(request,context=context)
# ssl.CertificateError: hostname 'www.12306.cn' doesn't match either of ......
with res:
print(res._method)
print(res.geturl())
print(res.read().decode())
urllib3庫
https://urllib3.readthedocs.io/en/latest/
標準庫urlib缺少了一些關鍵的功能,非標準庫的第三方庫urllib3提供了,比如說連接池管理。
- 安裝
pip install urlib3
import urllib3
from urllib3.response import HTTPResponse
url = "https://movie.douban.com"
ua = "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN) AppleWebKit/537.36 (KHTML, like Gecko) Version/5.0.1 Safari/537.36"
# 鏈接池管理
with urllib3.PoolManager() as http:
response:HTTPResponse = http.request("GET",url,headers={"User-Agent":ua})
print(type(response))
print(response.status,response.reason)
print(response.headers)
print(response.data[:50])
requests庫
- requests使用了urllib3,但是API更加友好,推薦使用。
- 安裝
pip install requests
import requests
ua = "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36"
url = "https://movie.douban.com/"
response = requests.request("GET",url,headers={"User-Agent":ua})
with response:
print(type(response))
print(response.url)
print(response.status_code)
print(response.request.headers) #請求頭
print(response.headers) #響應頭
response.encoding = "utf-8"
print(response.text[:200]) #HTML的內容
with open('d:/movie.html',"w",encoding='utf-8') as f:
f.write(response.text)
- requests默認使用Session對象,是爲了多次和服務器端交互中保留會話的信息,例如:cookie。
#直接使用Session
import requests
ua = "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:50.0) Gecko/20100101 Firefox/50.0"
urls = ["https://www.baidu.com/s?wd=xdd","https://www.baidu.com/s?wd=xdd"]
session = requests.Session()
with session:
for url in urls:
response = session.get(url,headers = {"User-Agent":ua})
# response = requests.request("GET",url,headers={"User-Agent":ua}) #觀察兩種方式區別
with response:
print(response.request.headers) #請求頭
print(response.cookies) #響應的cookie
print(response.text[:20]) #HTML的內容
print("-"*30)
- 使用session訪問,第二次帶上了cookie