爬蟲
什麼是爬蟲?
網絡爬蟲(又被稱爲網頁蜘蛛,網絡機器人)就是模擬客戶端發送網絡請求,接收請求響應,
一種按照必定的規則,自動地抓取互聯網信息的程序。
原則上,只要是瀏覽器(客戶端)能作的事情,爬蟲都可以作
爬蟲的分類:
通用爬蟲 :一般指搜索引擎的爬蟲
聚焦爬蟲 :針對特定網站的爬蟲(重點)
聚焦爬蟲的具體流程:
構造url
發起請求獲取響應
提取數據
保存數據
搜索引擎的工做流程:
抓取網頁
數據存儲
預處理
提供檢索服務網站排名
搜索引擎的侷限性:
通用搜索引擎所返回的網頁裏80%的內容無用
圖片、音頻、視頻多媒體的內容通用搜索引擎無能爲力
不一樣用戶搜索的目的不全相同,可是返回內容相同
robots協議
網站經過robots協議告訴搜索引擎那些數據能夠搜索,那些不能抓取,但僅僅只是道德方面的約束
具體以下:

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: /
View Code
http和https的區別
端口不一樣(http:80,https:443)
安全程度不一樣,https更安全些
瀏覽器發送http/https的請求過程:
先找到DNS服務器解析域名,在找對應的服務器
url的形式
url的形式:scheme://host[:port#]/path/…/[?query-string][#anchor]
scheme:協議(例如:http, https, ftp)
host:服務器的IP地址或者域名
port:服務器的端口(若是是走協議默認端口,80 or 443)
path:訪問資源的路徑
query-string:參數,發送給http服務器的數據
anchor:錨(跳轉到網頁的指定錨點位置)
http://localhost:4000/file/part01/1.2.html
http://item.jd.com/11936238.html#product-detail
url地址中是否包含錨點對響應沒有影響
HTTP常見請求頭
Host (主機和端口號)
Connection (連接類型)
Upgrade-Insecure-Requests (升級爲HTTPS請求)
User-Agent (瀏覽器名稱)
Accept (傳輸文件類型)
Referer (頁面跳轉處)
Accept-Encoding(文件編解碼格式)
Cookie (Cookie)
x-requested-with :XMLHttpRequest (是Ajax 異步請求)
post和get請求的區別
get提交數據時,受到提交數據量的影響,而post並無限制
get請求是將數據放在url中傳遞,而post請求時將數據放在請求體中來傳遞
get能被緩存而post不能被緩存
get只容許asill碼而post並無過多的限制
常見的響應狀態碼:
200:成功
302:臨時轉移至新的url
307:臨時轉移至新的url
404:not found
403: 人家不想給你數據
500:服務器內部錯誤
Unicode UTF8 ASCII的區別:
字符(Character)是各類文字和符號的總稱,包括各國家文字、標點符號、圖形符號、數字等
字符集(Character set)是多個字符的集合
字符集包括:ASCII字符集、GB2312字符集、GB18030字符集、Unicode字符集等
ASCII編碼是1個字節,而Unicode編碼一般是2個字節。
UTF-8是Unicode的實現方式之一,UTF-8是它是一種變長的編碼方式,能夠是1,2,3個字節
python3中兩種字符串類型:
str : unicode的呈現形式
bytes :字節類型,互聯網上數據的都是以二進制的方式(字節類型)傳輸的
使用方法:
str 使用encode方法轉化爲 bytes
bytes 經過decode轉化爲 str
編碼方式解碼方式必須同樣,不然就會出現亂碼
python2中字符串有兩種類型
unicode類型
字節類型
在Python2中,字符串沒法徹底地支持國際字符集和Unicode編碼。爲了解決這種限制,
Python2對Unicode數據使用了單獨的字符串類型。要輸入Unicode字符串字面量,要在第一個引號
前加上'u'。
Python2中普通字符串實際上就是已經編碼(非Unicode)的字節字符串。
request模塊的使用
爲何通常用request的比較多?
requests的底層實現就是urllib
requests在python2 和python3中通用,方法徹底同樣
requests簡單易用
Requests可以自動幫助咱們解壓(gzip壓縮的等)網頁內容
做用:
發送網絡請求,返回響應數據
response = requests.get(url)後
response的經常使用屬性:
response.text
respones.content
response.status_code
response.request.headers
response.headers
response.text 和response.content的區別
response.text
類型:str
解碼類型: 根據HTTP 頭部對響應的編碼做出有根據的推測,推測的文本編碼
如何修改編碼方式:response.encoding="gbk"
response.content
類型:bytes
解碼類型: 沒有指定
如何修改編碼方式:response.content.deocde("utf8")
獲取網頁源碼的通用三種方式:
response.content.decode()
response.content.decode("GBK")
response.text
request模塊的深刻使用
使用requests模塊發送post請求
用法: response = requests.post(url=url,data = data,headers=headers)
data 的形式:字典
使用代理IP技術
爲何要使用代理
讓服務器覺得不是同一個客戶端在請求
防止咱們的真實地址被泄露,防止被追究
代理的分類
正向代理
已經知道服務器的IP地址時,發出的請求
反向代理
瀏覽器不知道服務器的真實地址,如:nginx
代理的使用
經常使用的代理網站:
https://proxy.mimvp.com/
https://www.kuaidaili.com/
http://www.goubanjia.com/
http://www.ip3366.net/
使用方法一:
用法: requests.get("http://www.baidu.com", proxies = proxies)
proxies的形式:字典
例如:
proxies = {
"http": "http://12.34.56.79:9527",
"https": "https://12.34.56.79:9527",
}
使用代理二:
代理池技術

import requests
#導入random,對ip池隨機篩選
import random
proxy = [
{
'http': 'http://61.135.217.7:80',
'https': 'http://61.135.217.7:80',
},
{
'http': 'http://118.114.77.47:8080',
'https': 'http://118.114.77.47:8080',
},
{
'http': 'http://112.114.31.177:808',
'https': 'http://112.114.31.177:808',
},
{
'http': 'http://183.159.92.117:18118',
'https': 'http://183.159.92.117:18118',
},
{
'http': 'http://110.73.10.186:8123',
'https': 'http://110.73.10.186:8123',
},
]
url = '爬取連接地址'
response = requests.get(url,proxies=random.choice(proxy))
View Code
代理IP的分類
根據代理服務器端的配置,向目標地址發送請求時,REMOTE_ADDR, HTTP_VIA,HTTP_X_FORWARDED_FOR三個變量不一樣而能夠分爲下面四類:
透明代理(Transparent Proxy)
REMOTE_ADDR = Proxy IP
HTTP_VIA = Proxy IP
HTTP_X_FORWARDED_FOR = Your IP
透明代理雖然能夠直接「隱藏」你的IP地址,可是仍是能夠從HTTP_X_FORWARDED_FOR來查到你是誰。
匿名代理(Anonymous Proxy)
REMOTE_ADDR = proxy IP
HTTP_VIA = proxy IP
HTTP_X_FORWARDED_FOR = proxy IP
匿名代理比透明代理進步了一點:別人只能知道你用了代理,沒法知道你是誰。
混淆代理(Distorting Proxies)
REMOTE_ADDR = Proxy IP
HTTP_VIA = Proxy IP
HTTP_X_FORWARDED_FOR = Random IP address
如上,與匿名代理相同,若是使用了混淆代理,別人仍是能知道你在用代理,可是會獲得
一個假的IP地址,假裝的更逼真
高匿代理(Elite proxy或High Anonymity Proxy)
REMOTE_ADDR = Proxy IP
HTTP_VIA = not determined
HTTP_X_FORWARDED_FOR = not determined
能夠看出來,高匿代理讓別人根本沒法發現你是在用代理,因此是最好的選擇。
從使用的協議:代理ip能夠分爲http代理,https代理,socket代理等,使用的時候須要根據
抓取網站的協議來選擇
代理IP使用的注意點
反反爬
使用代理ip是很是必要的一種反反爬的方式,可是即便使用了代理ip,對方服務器任然會
有不少的方式來檢測咱們是不是一個爬蟲
好比:
一段時間內,檢測IP訪問的頻率,訪問太多頻繁會屏蔽
檢查Cookie,User-Agent,Referer等header參數,若沒有則屏蔽
服務方購買全部代理提供商,加入到反爬蟲數據庫裏,若檢測是代理則屏蔽
因此更好的方式是購買質量更高的代理,或者本身搭建代理服務器,組裝本身的代理IP池,
同時在使用的時候使用隨機的方式進行選擇使用,不要每次都用一個代理ip,沒事沒有
任何效果的,
代理ip池的更新,
購買的代理ip不少時候大部分(超過60%)可能都沒辦法使用,這個時候就須要經過程序
去檢測哪些可用,把不能用的刪除掉。對應的實現方式在咱們學習了超時參數的使用
以後你們會了解
requess模塊處理cookie相關的請求
cookie和session的區別:
cookie數據存放在客戶的瀏覽器上,session數據放在服務器上。
cookie不是很安全,別人能夠分析存放在本地的cookie並進行cookie欺騙。
session會在必定時間內保存在服務器上。當訪問增多,會比較佔用你服務器的性能。
單個cookie保存的數據不能超過4K,不少瀏覽器都限制一個站點最多保存20個cookie
爬蟲中爲何要使用cookie
帶上cookie的好處
可以訪問登陸後的頁面
正常的瀏覽器在請求服務器的時候確定會帶上cookie(第一次請求某個地址除外),
因此對方服務器有可能會經過是否攜帶cookie來判斷咱們是不是一個爬蟲,對應的
可以起到必定的反爬的效果
帶上cookie的壞處
一套cookie每每對應的是一個用戶的信息,請求太頻繁有更大的可能性被對方識別爲爬蟲
那麼上面的問題如何解決 ?使用多個帳號
requests處理cookie相關的請求之session
requests 提供了一個叫作session類,來實現客戶端和服務端的會話保持
會話保持有兩個內涵:
保存cookie
實現和服務端的長鏈接
使用方法
session = requests.session()
response = session.get(url,headers)
session實例在請求了一個網站後,對方服務器設置在本地的cookie會保存在session中,
下一次再使用session請求對方服務器的時候,會帶上前一次的cookie
requests處理cookie相關的請求之cookie放在headers中
注意:
headers中的cookie:
使用分號(;)隔開
分號兩邊的相似a=b形式的表示一條cookie
a=b中,a表示鍵(name),b表示值(value)
在headers中僅僅使用了cookie的name和value
在headers中使用cookie
headers = {
"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"Cookie":" Pycharm-26c2d973=dbb9b300-2483-478f-9f5a-16ca4580177e; Hm_lvt_98b9d8c2fd6608d564bf2ac2ae642948=1512607763; Pycharm-26c2d974=f645329f-338e-486c-82c2-29e2a0205c74; _xsrf=2|d1a3d8ea|c5b07851cbce048bd5453846445de19d|1522379036"}
requests.get(url,headers=headers)
cookie有過時時間,因此直接複製瀏覽器中的cookie可能意味着下一程序繼續運行的時候
須要替換代碼中的cookie,對應的咱們也能夠經過一個程序專門來獲取cookie供其餘
程序使用;固然也有不少網站的cookie過時時間很長,這種狀況下,直接複製cookie來使用
更加簡單
requests處理cookie相關的請求之使用cookies參數
cookies的形式:字典
cookies = {"cookie的name":"cookie的value"}
使用方法:
requests.get(url,headers=headers,cookies=cookie_dict}
js的執行
用requests模塊來獲取cookie
requests.utils.dict_from_cookiejar:把cookiejar對象轉化爲字典
import requests
url = "http://www.baidu.com"
response = requests.get(url)
print(type(response.cookies))
cookies = requests.utils.dict_from_cookiejar(response.cookies)
print(cookies)
輸出爲:
<class 'requests.cookies.RequestsCookieJar'>
{'BDORZ': '27315'}
用requests模塊來處理證書的不安全性
import requests
url = "https://hao123.com"
# 可使用當前網站
response = requests.get(url,verify=False)
超時參數的使用
使用方法以下:
response = requests.get(url,timeout=3)
經過添加timeout參數,可以保證在3秒鐘內返回響應,不然會報錯
retrying模塊的使用
用retrying模塊提供的retry模塊
經過裝飾器的方式使用,讓被裝飾的函數反覆執行
retry中能夠傳入參數stop_max_attempt_number,讓函數報錯後繼續從新執行,
達到最大執行次數的上限,若是每次都報錯,整個函數報錯,若是中間有一個成功,
程序繼續日後執行
代碼以下:
import requests
from retrying import retry
headers = {}
@retry(stop_max_attempt_number=3) #最大重試3次,3次所有報錯,纔會報錯
def _parse_url(url)
response = requests.get(url, headers=headers, timeout=3) #超時的時候回報錯並重試
assert response.status_code == 200 #狀態碼不是200,也會報錯並充實
return response
def parse_url(url)
try: #進行異常捕獲
response = _parse_url(url)
except Exception as e:
print(e)
response = None
return response
數據提取的概念和方式
什麼是數據提取
簡單的來講,數據提取就是從響應中獲取咱們想要的數據的過程
爬蟲中數據的分類
先去看網頁返回的數據類型是什麼樣的
若是網頁的標題中的文本在網頁的源代碼中通常是屬於非結構化數據
結構化數據:json,xml等
處理方式:直接轉化爲python類型
非結構化數據:HTML
處理方式:正則表達式、xpath
XML數據:

<bookstore>
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="CHILDREN">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>
View Code
數據提取之JSON
JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式,它使得人們很容易的進行閱讀和編寫。同時也方便了機器進行
解析和生成。適用於進行數據交互的場景,好比網站前臺與後臺之間的數據交互。
url中含有jsonp的字符串
能夠直接去除url中的jsonp({xxxx}),這樣作的好處是直接返回的是json的數據格式
;jsonp1({"count": 18, "start": 0, "subject_collection_items": "...."})
其中jsonp1彷佛是很眼熟,在請求的地址中包含一個參數callback=jsonp1,
正是因爲這個參數的存在,才致使結果中也會有這部分數據,對應的解決方法是:
直接刪除url地址中的callback字段便可,在url地址中不少字段都是沒用的,
好比這裏的&loc_id,_等等,能夠大膽嘗試
咱們如何肯定數據在哪裏
在url地址對應的響應中搜索關鍵字便可
可是注意:url地址對應的響應中,中文每每是被編碼以後的內容,
因此更推薦你們去搜索英文和數字;另一個方法就是在perview中搜索,
其中的內容都是轉碼以後的
切換手機版尋找返回json的地址
在chrome中點擊切換手機版的選項,須要從新刷新頁面纔可以切換成功,
部分網站還須要從新進入主頁面以後再繼續點擊纔可以切換成功,好比:豆瓣熱映
如今咱們找到了返回電影數據的地址:https://m.douban.com/rexxar/api/v2/subject_collection/movie_showing/items?os=android&for_mobile=1&callback=jsonp1&start=0&count=18&loc_id=108288&_=1524495777522
經過請求咱們發現,並不能成功,那是爲何呢?
對比抓包的地址和如今的地址,會發現部分headers字段沒有,經過嘗試,
會發現是Referer字段缺乏的緣由
JSON的數據轉換方式
loads 將字符串轉換爲Python的數據類型
load 將包含json的類文件對象轉換爲Python的數據類型
dumps 將Python的數據類型專換爲字符串
dump 將Python的數據類型轉換爲包含json的類文件對象
其中類文件對象的理解:
具備read()或者write()方法的對象就是類文件對象,好比
f = open(「a.txt」,」r」) f就是類文件對象
具體使用方法:
#json.dumps 實現python類型轉化爲json字符串
#indent實現換行和空格
#ensure_ascii=False實現讓中文寫入的時候保持爲中文
json_str = json.dumps(mydict,indent=2,ensure_ascii=False)
#json.loads 實現json字符串轉化爲python類型
my_dict = json.loads(json_str)
#json.dump 實現把python類型寫入類文件對象
with open("temp.txt","w") as f:
json.dump(mydict,f,ensure_ascii=False,indent=2)
# json.load 實現類文件對象中的json字符串轉化爲python類型
with open("temp.txt","r") as f:
my_dict = json.load(f)
數據提取之正則
- 正則的語法
- . 匹配到出了\n以外的全部字符,re.S模式下能夠匹配\n
- \ 轉義
- [] 或,選擇其中的一個字符
- | 或,選擇|兩邊的內容
- * 匹配0次或者屢次
- + 匹配一次或者屢次
- ? 費貪婪
- \s 空白字符,包含空格,\n,
- \d 數字
- re模塊的經常使用方法
- re.findall("regex","str") #返回列表
- re.sub("regex","_","str") #返回字符串
- re.compile("regex",re.S) # 編譯,提升匹配效率
- 原始字符串r
- 相對於特殊符號而言,表示特殊符號的字面意思
- 用途
- 正則:忽略轉義符號帶來的印象,加上r以後照着寫\
- windows下文件路徑
數據提取之xpath和lxml
xpath語法:
- // 的用途
- //a 當前html頁面上的全部的a
- bookstore//book bookstore下的全部的book元素
- @ 的使用
- //a/@href 全部的a的href
- //title[@lang="eng"] 選擇lang=eng的title標籤
- text() 的使用
- //a/text() 獲取全部的a下的文本
- //a[text()='下一頁'] 獲取文本爲下一頁的a標籤
- a//text() a下的全部的文本
- xpath查找特定的節點
- //a[1] 選擇第一個
- //a[last()] 最後一個
- //a[position()<4] 前三個
具體用法以下:
items["username"] = div.xpath('.//h2/text()')[0]
items["age"] = div.xpath('.//div[@class="articleGender womenIcon"]/text()')[0] if len(div.xpath('.//div[@class="articleGender womenIcon"]/text()'))>0 else None
items["text"] = div.xpath('.//div[@class="content"]/span/text()')
items["laugh"] = div.xpath('.//span[@class="stats-vote"]/i/text()')[0]
items["comment"] = div.xpath('.//div[@class="main-text"]/text()')
items["comment_author"] =
div.xpath('.//div[@class="cmtMain"]//span[@class="cmt-name"]/text()')
安裝lxml庫
pip install lxml
from lxml import etree
element = etree.HTML(html_str) #bytes或者str類型的字符串
element.xpath("xpath str") #返回列表
etree.tostring(element) #轉化爲字符串
#數據提取時:先分組,在提取,要否則數據對不齊,保存時會錯位,會出現比較嚴重的問題
提升爬蟲的效率
多線程
多進程
協程
線程池
進程池
隊列的使用
from queue import Queue
q = Queue(maxsize=100)
item = {}
q.put_nowait(item) #不等待直接放,隊列滿的時候會報錯
q.put(item) #放入數據,隊列滿的時候回等待
q.get_nowait() #不等待直接取,隊列空的時候會報錯
q.get() #取出數據,隊列爲空的時候會等待
q.qsize() #獲取隊列中現存數據的個數
q.join() #隊列中維持了一個計數,計數不爲0時候讓主線程阻塞等待,隊列計數爲0的時候纔會繼續日後執行
q.task_done() #put的時候計數+1,get不會-1,get須要和task_done 一塊兒使用纔會-1from queue import Queue