Python_爬蟲基礎

alogy發表於2019-02-16

爬蟲概念

資料獲取的方式:

  • 企業生產的使用者資料:大型網際網路公司有海量使用者,所以他們積累資料有天然優勢。有資料意識的中小型企業,也開始積累的資料。
  • 資料管理諮詢公司
  • 政府/機構提供的公開資料
  • 第三方資料平臺購買資料
  • 爬蟲爬取資料

什麼是爬蟲

抓去網頁資料的程式

網頁三大特徵:

  • 每個網頁都有自己的URL
  • 網頁都使用HTML標記語言來描述頁面資訊
  • 網頁都使用HTTP/HTTPS協議來傳輸HTML資料

爬蟲的設計思路

  1. 確定需要爬取的網頁URL地址
  2. 通過HTTP/HTTPS協議來獲取對應的HTML頁面
  3. 提取HTML頁面中的資料
    如果是需要的資料,就儲存起來
    如果頁面是其它URL,那就繼續爬取

如何抓取HTML頁面
HTTP協議請求的處理,urllib, urllib2, requests,處理後的請求可以模擬瀏覽器傳送請求,獲取伺服器響應的檔案

解析伺服器響應的內容
re, xpath(常用), BeautifulSoup4(bs4), jsonpath, pyquery等使用某種描述性一樣來給需要提取的資料定義一個匹配規則,符合這個規則的資料就會被匹配。

如何採集動態HTML,驗證碼的處理
Selenium(自動化測試工具) + PhantomJS(無介面瀏覽器)
驗證碼處理通過Tesseract: 機器影像識別系統(圖片中的文字識別)

Scrapy框架
(Scrapy, Pyspider)

  • 高效能高定製型(非同步網路框架twisted),所以資料下載速度快
  • 提供了資料儲存,資料下載,提取規則等元件

分散式策略

  1. 是否有那麼多的機器去做分散式?
  2. 獲取的資料是否值得搭建分散式系統?

使用scrapy-redis來搭建,在Scrapy的基礎上新增了一套 Redis資料庫為核心的一套元件,讓Scrapy框架支援分散式的功能。主要在Redis中做請求指紋去重請求分配資料臨時儲存

爬蟲 – 反爬蟲 – 反反爬蟲

反爬蟲: User-Agent, IP, 代理, 驗證碼, 動態資料載入, 加密資料
資料的價值,是否值得去費勁去做反爬蟲,一般做到代理階段或封IP
機器成本 + 人力成本 > 資料價值

爬蟲和反爬蟲之間的鬥爭,最後一定是爬蟲獲勝。
只要是真實使用者可以瀏覽的網頁資料,爬蟲就一定能爬下來。(爬蟲模擬瀏覽器獲取資料)

爬蟲集合awesome-spider

通用爬蟲

搜尋引擎使用的爬蟲系統

目標:儘可能把網際網路上所有的網頁下載下來,放到本地伺服器裡形成備份,再對這些網頁做相關處理(提取關鍵字,去掉廣告),最後提供一個使用者檢索介面

抓取流程:

  • 首先選取一部分已有的URL,把這些URL放到待爬取佇列。
  • 從佇列裡去取出這些URL,然後解析DNS得到主機IP,然後去這個IP對應的伺服器下載HTML頁面,儲存到搜尋引擎的本地伺服器裡,之後把這個已經爬過的URL放入到已經爬取佇列中
  • 分析網頁內容,找出網頁中的其它URL內容,繼續爬取。

搜尋引擎如何獲取一個新網站的URL:

  • 主動向搜尋引擎提交網址: 百度搜尋資源平臺
  • 在其它網站設定外鏈
  • 搜尋引擎會和DNS服務商進行合作,可以快速收錄新的網站

通用爬蟲並不是萬物皆可爬,它也需要遵守規則:
Robots協議,協議會指明通用爬蟲可以爬取網頁的許可權。
Robots.txt並不是所有爬蟲都遵守,一般只有大型搜尋引擎爬蟲才會遵守。

通用爬蟲工作流程:
爬取網頁 -> 儲存資料 -> 內容處理 -> 提供檢索/排名服務

搜尋引擎排名:

  • PageRank值:根據網站的流量(pv),流量越高,排名約靠前
  • 競價排名

通用爬蟲的缺點:

  1. 只能提供和文字相關的內容(HTML,Word,PDF)等,但是不能提供多媒體(音樂,視訊,圖片)和二進位制檔案。
  2. 提供的結果千篇一律,不能針對不同領域的人提供不同的搜尋結果。
  3. 不能理解人類語義上的檢索。

DNS: 把域名解析成IP

聚焦爬蟲

爬蟲程式設計師寫的針對某種內容爬蟲。(針對通用爬蟲的缺點)

面向主題爬蟲,面向需求爬蟲,會針對某種特定的內容去爬取資訊,而且會保證內容資訊和需求儘可能相關。

HTTP&HTTPS

HTTP協議(HyperText Transfer Protocol,超文字傳輸協議):是一種釋出和接收HTML頁面的方法。

HTTPS(Hypertext Transfer Protocol over Secure Socket Layer)簡單講是HTTP的安全版,在HTTP下加入SSL

SSL(Secure Sockets Layer 安全套接層)主要用於Web的安全傳輸協議,在傳輸層對網路連線進行加密,保障在Internet上資料傳輸的安全。

  • HTTP的埠號為80
  • HTTPS的埠號為443

HTTP工作原理

網路爬蟲抓取過程可以理解為模擬瀏覽器操作的過程

瀏覽器的主要功能是向伺服器發出請求,在瀏覽器視窗中展示您選擇的網路資源,HTTP是一套計算機通過網路進行通訊的規則。

常用的請求報頭:

  • Host (主機和埠號): 對應網址URL中的Web名稱和埠號,用於指定被請求資源的Internet主機和埠號,通常屬於URL的一部分。
  • Connection (連結型別): 表示客戶端與服務連線型別

    1. Client 發起一個包含 Connection:keep-alive 的請求,HTTP/1.1使用 keep-alive 為預設值。
    2. Server收到請求後:如果 Server 支援 keep-alive,回覆一個包含 Connection:keep-alive 的響應,不關閉連線; 如果 Server 不支援keep-alive,回覆一個包含 Connection:close 的響應,關閉連線。
    3. 如果client收到包含 Connection:keep-alive 的響應,向同一個連線傳送下一個請求,直到一方主動關閉連線。
    4. keep-alive在很多情況下能夠重用連線,減少資源消耗,縮短響應時間,比如當瀏覽器需要多個檔案時(比如一個HTML檔案和相關的圖形檔案),不需要每次都去請求建立連線。
  • Upgrade-Insecure-Requests (升級為HTTPS請求): 升級不安全的請求,意思是會在載入 http 資源時自動替換成 https 請求,讓瀏覽器不再顯示https頁面中的http請求警報。(HTTPS 是以安全為目標的 HTTP 通道,所以在 HTTPS 承載的頁面上不允許出現 HTTP 請求,一旦出現就是提示或報錯。)
  • User-Agent (瀏覽器名稱): 是客戶瀏覽器的名稱
  • Accept (傳輸檔案型別): 指瀏覽器或其他客戶端可以接受的MIME(Multipurpose Internet Mail Extensions(多用途網際網路郵件擴充套件))檔案型別,伺服器可以根據它判斷並返回適當的檔案格式。

    1. Accept: */*:表示什麼都可以接收。
    2. Accept:image/gif:表明客戶端希望接受GIF影像格式的資源;
    3. Accept:text/html:表明客戶端希望接受html文字。
    4. Accept: text/html, application/xhtml+xml;q=0.9, image/*;q=0.8:表示瀏覽器支援的 MIME 型別分別是 html文字、xhtml和xml文件、所有的影像格式資源。html中檔案型別的accept屬性有哪些
  • Referer (頁面跳轉處): 表明產生請求的網頁來自於哪個URL,使用者是從該 Referer頁面訪問到當前請求的頁面。這個屬性可以用來跟蹤Web請求來自哪個頁面,是從什麼網站來的等。有時候遇到下載某網站圖片,需要對應的referer,否則無法下載圖片,那是因為人家做了防盜鏈,原理就是根據referer去判斷是否是本網站的地址,如果不是,則拒絕,如果是,就可以下載;
  • Accept-Encoding(檔案編解碼格式): 指出瀏覽器可以接受的編碼方式。編碼方式不同於檔案格式,它是為了壓縮檔案並加速檔案傳遞速度。瀏覽器在接收到Web響應之後先解碼,然後再檢查檔案格式,許多情形下這可以減少大量的下載時間。例如:Accept-Encoding:gzip;q=1.0, identity; q=0.5, *;q=0
  • Accept-Language(語言種類): 指出瀏覽器可以接受的語言種類,如en或en-us指英語,zh或者zh-cn指中文,當伺服器能夠提供一種以上的語言版本時要用到。
  • Accept-Charset(字元編碼): 指出瀏覽器可以接受的字元編碼。例如:Accept-Charset:iso-8859-1,gb2312,utf-8
  • Cookie (Cookie): 瀏覽器用這個屬性向伺服器傳送Cookie。Cookie是在瀏覽器中寄存的小型資料體,它可以記載和伺服器相關的使用者資訊,也可以用來實現會話功能
  • Content-Type (POST資料型別): POST請求裡用來表示的內容型別。例如:Content-Type = Text/XML; charset=gb2312:

常用的響應報頭(瞭解):

  • Cache-Control:must-revalidate, no-cache, private: 告訴客戶端,服務端不希望客戶端快取資源,在下次請求資源時,必須要從新請求伺服器,不能從快取副本中獲取資源。
  • Connection:keep-alive: 客戶端伺服器的tcp連線也是一個長連線,客戶端可以繼續使用這個tcp連線傳送http請求
  • Content-Encoding:gzip: 告訴客戶端,服務端傳送的資源是採用gzip編碼的,客戶端看到這個資訊後,應該採用gzip對資源進行解碼。
  • Content-Type:text/html;charset=UTF-8: 告訴客戶端,資原始檔的型別,還有字元編碼,客戶端通過utf-8對資源進行解碼,然後對資源進行html解析。
  • Date:Sun, 21 Sep 2016 06:18:21 GMT: 服務端傳送資源時的伺服器時間,GMT是格林尼治所在地的標準時間。http協議中傳送的時間都是GMT的,這主要是解決在網際網路上,不同時區在相互請求資源的時候,時間混亂問題。
  • Expires:Sun, 1 Jan 2000 01:00:00 GMT: 這個響應頭也是跟快取有關的,告訴客戶端在這個時間前,可以直接訪問快取副本,很顯然這個值會存在問題,因為客戶端和伺服器的時間不一定會都是相同的,如果時間不同就會導致問題。所以這個響應頭是沒有Cache-Control:max-age=*這個響應頭準確的,因為max-age=date中的date是個相對時間.
  • Pragma:no-cache: 這個含義與Cache-Control等同。
  • Server:Tengine/1.4.6: 這個是伺服器和相對應的版本,只是告訴客戶端伺服器的資訊。
  • Transfer-Encoding:chunked: 這個響應頭告訴客戶端,伺服器傳送的資源的方式是分塊傳送的。

響應狀態碼:

  • 100~199:表示伺服器成功接收部分請求,要求客戶端繼續提交其餘請求才能完成整個處理過程。
  • 200~299:表示伺服器成功接收請求並已完成整個處理過程。常用200(OK 請求成功)。
  • 300~399:為完成請求,客戶需進一步細化請求。例如:請求的資源已經移動一個新地址、常用302(所請求的頁面已經臨時轉移至新的url)、307和304(使用快取資源)。
  • 400~499:客戶端的請求有錯誤,常用404(伺服器無法找到被請求的頁面)、403(伺服器拒絕訪問,許可權不夠)。
  • 500~599:伺服器端出現錯誤,常用500(請求未完成。伺服器遇到不可預知的情況)。

Cookie 和 Session:
因為伺服器和客戶端的互動僅限於請求/響應過程,結束之後便斷開,在下一次請求時,伺服器會認為新的客戶端。為了維護他們之間的連結,讓伺服器知道這是前一個使用者傳送的請求,必須在一個地方儲存客戶端的資訊

Cookie:通過在客戶端 記錄的資訊確定使用者的身份。
Session:通過在伺服器端 記錄的資訊確定使用者的身份。

urllib

urllib.request

linux中的py原始碼檔案位置:
python自帶:vim /usr/lib/python2.7/urllib2.py
pip安裝:vim /usr/local/lib/python3.6/site-packages/django/http/cookie.py

urllib2.urlopen

# -*- coding:utf-8 -*-

import urllib.request as urllib2

# 返回類檔案物件
response = urllib2.urlopen(`http://www.baidu.com/`)
# urlopen不支援構造

# 伺服器返回類檔案物件支援python檔案物件的操作方法
# read()方法就是讀取檔案裡面的全部內容,返回字串
html = response.read()

print(html)

Request

# -*- coding:utf-8 -*-

import urllib.request as urllib2


ua_headres = {
    `User_Agent`: `Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Mobile Safari/537.36`
}

# urllib2.Request(url, data, headres)
# 通過urllib2.Request()方法構造一個請求物件
requset = urllib2.Request(`http://www.baidu.com`, headers=ua_headres)


# 返回類檔案物件, urlopen不支援構造
response = urllib2.urlopen(requset)

# 伺服器返回類檔案物件支援python檔案物件的操作方法
# read()方法就是讀取檔案裡面的全部內容,返回字串
html = response.read()

print(html)

User_Agent,是傳送請求必須帶的請求頭

Response響應

response是伺服器響應的類檔案,除了支援檔案操作的方法外,常用的方法也有:
respnse.getcode(), response.geturl(), response.info()

#condig=utf-8

import urllib.request as urllib2

# print(dir(urllib2))

ua_headres = {
    `User-Agent`: `Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.104 Safari/537.36 Core/1.53.4620.400 QQBrowser/9.7.13014.400`        
}

request = urllib2.Request(`http://www.baidu.com/`, headers=ua_headres)

response = urllib2.urlopen(request)

html = response.read()

# 返回HTTP的響應碼,成功返回200
# 4 伺服器頁面出錯, 5 伺服器問題
print(response.getcode())

# 返回實際資料的url,防止重定向問題
print(response.geturl())

# 返回伺服器響應報頭資訊
print(response.info())

# print(dir(response))

User-Agent歷史

如果用一個合法的身份去請求別人網站,就是歡迎的,所以就應該給這個程式碼加上一個身份,就是所謂的User-Agent頭。

urllib2預設的User-Agent頭為:Python-urllib/x.yxyPython主版本和次版本號,例如 Python-urllib/2.7

Mosaic世界上第一個瀏覽器:美國國家計算機應用中心
Netscape,網景:Netscape(支援框架)
Microsoft微軟:Internet Explorer

第一次瀏覽器大戰:網景公司失敗

Mozilla 基金組織:Firefox 火狐 核心(Gecko核心)(瀏覽器支援核心開始,User-Agent開始逐漸使用)

User-Agent 決定使用者的瀏覽器,為了獲取更好的HTML頁面效果

IE就給自己披著了個Mozilla的外皮

核心:

  • Mozilla: Firefox (Gecko)
  • IE: Trident
  • Opera: Presto
  • Linux: KHTML (like Gecko)
  • Apple: Webkit (like KTML)
  • Google: Chrome (like webkit)

add_header() & get_header()

add_header(): 新增/修改 一個HTTP報頭
get_header(): 獲取一個已有的HTTP報頭值,只能第一個字母大寫,其它的必須小寫

# -*- coding:utf-8 -*-

import urllib.request as urllib2
import random

url = `http://www.baidu.com/`

# 可以是User-Agent列表,也可以是代理列表。 作用:反反爬蟲
ua_list = [
    `Mozilla/5.0 (Windows NT 6.1; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0`,
    `Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.57.2 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2`,
    `Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36`,
    `Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.50`
]

# 在User-Agent列表裡隨機選擇一個User-Agent
user_agent = random.choice(ua_list)

# 構造一個請求
request = urllib2.Request(url)

# add_header()方法,新增/修改 一個HTTP報頭
request.add_header(`User-Agent`, user_agent)

# get_header() 獲取一個已有的HTTP報頭值,只能第一個字母大寫,其它的必須小寫
request.get_header(`User-agent`)

response = urllib2.urlopen(request)

html = response.read()
print(html)

urllib.urlencode

編碼:
urlencode位置:urllib.parse.urlencode(values)。 其中values所需要編碼的資料,引數只能為字典
解碼:
unquote: urllib.parse.unquote(values)

#conding=utf-8

import urllib.parse

test = {
    `test`: `我的`
}

# 通過urllib.urlencode()方法,將字典鍵值對按URL編碼轉換,從而能被web伺服器接受。
enCodeTest = urllib.parse.urlencode(test)

# 冒號解析為等號
print(enCodeTest) # test=%E6%88%91%E7%9A%84

# 通過urllib.unquote()方法,把 URL編碼字串,轉換回原先字串。
print(urllib.parse.unquote(enCodeTest)) # test=我的

爬取百度貼吧起始頁到結束頁的案例

#conding=utf-8

import urllib.request
import urllib.parse

def loadPage(url, filename):
    ```
        作用: 根據url傳送請求,獲取伺服器響應檔案
        url: 需要爬取的url地址
        filename: 處理的檔名
    ```
    print(`正在下載` + filename)
    headers = {
        `User-Agent`: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36`
    }
    request = urllib.request.Request(url, headers=headers)
    return urllib.request.urlopen(request).read()
    

def writePage(html, filenmae):
    ```
        作用:將html內容寫入到本地
        html: 伺服器響應檔案內容
    ```
    print(`正在儲存` + filenmae)
    # 檔案寫入
    with open(filenmae, `w`) as f: #  with 之後,不需要做檔案關閉還有其它上下文處理的操作 等同於 open(), write(), close()
        f.write(html.decode(`utf-8`))
    print(`-` * 30)    
    print(`thanks`)


def tiebaSpider(url, beginPage, endPage):
    ```
        作用: 貼吧爬蟲排程器,負責組合處理
        url: 貼吧url的前部分
        beginPage: 起始頁
        endPage: 結束頁
    ```
    for page in range(beginPage, endPage+1):
        pn = (page - 1) * 50
        filename = `第` + str(page) + `頁.html`
        fullurl = url + `&pn=` + str(pn)
        # print(fullurl)
        html = loadPage(fullurl, filename)
        writePage(html, filename)
        # print(html)

if __name__ == `__main__`:
    kw = input(`請輸入需要爬取的貼吧名:`)
    beginPage = int(input(`請輸入起始頁:`))
    endPage = int(input(`請輸入結束頁:`))
    
    # https://tieba.baidu.com/f?ie=utf-8&kw=javascirpt&fr=search
    url = `https://tieba.baidu.com/f?`
    key = urllib.parse.urlencode({`kw`: kw})
    fullurl = url + key

    tiebaSpider(fullurl, beginPage, endPage)

POST請求的模擬

GetPost請求的區別:

  • Get請求:查詢引數在QueryString裡儲存
  • Post請求:查詢引數在FormData中儲存

Post請求:

# -*- coding:utf-8 -*-

import urllib.request
import urllib.parse

url = `http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule`

key = input(`請輸入查詢翻譯的文字:`)

# 傳送到伺服器的表單資料,如果是中文需要轉碼
fromdata = {
    `i`: key,
    `from`: `AUTO`,
    `to`: `AUTO`,
    `smartresult`: `dict`,
    `client`: `fanyideskweb`,
    `salt`: `1528127663128`,
    `sign`: `c060b56b628f82259225f751c12da59a`,
    `doctype`: `json`,
    `version`: `2.1`,
    `keyfrom`: `fanyi.web`,
    `action`: `FY_BY_REALTIME`,
    `typoResult`: `false`
}

data = urllib.parse.urlencode(fromdata).encode(`utf-8`)

headers = {
    `User-Agent`: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36`
}

request = urllib.request.Request(url, data=data, headers=headers)

html = urllib.request.urlopen(request).read().decode()

print(html)

獲取AJAX載入的內容

AJAX一般返回的是JSON,直接對AJAX地址進行postget,就返回JSON資料了。

“作為一名爬蟲工程師,最需要關注的是資料的來源”

# -*- coding:utf-8 -*-

import urllib.request
import urllib.parse


url = `https://movie.douban.com/j/search_subjects?`
headers = {
    `User-Agent`: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36`
}
formdata = {
    `type`: `movie`,
    `tag`: `熱門`,
    `sort`: `recommend`,
    `page_limit`: 20,
    `page_start`: 40
}

data = urllib.parse.urlencode(formdata).encode(`utf-8`)
request = urllib.request.Request(url, data=data, headers=headers)
html = urllib.request.urlopen(request).read().decode()

print(html)

處理HTTPS請求 SSL證照驗證

網站的SSL證照是經過CA認證的,則能夠正常訪問
單獨處理SSL證照,讓程式忽略SSL證照驗證錯誤

# -*- coding:utf-8 -*-

import urllib.request
import ssl

# 表示忽略未經核實的SSL證照認證
context = ssl._create_unverified_context()

url = "https://www.12306.cn/mormhweb/"

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

request = urllib.request.Request(url, headers = headers)

# 在urlopen()方法裡 指明新增 context 引數
response = urllib.request.urlopen(request, context = context)

print(response.read())

CA: 數字證照認證中心的簡稱,是指發放、管理、廢除數字證照的受信任的第三方機構(類似與身份證)
CA的作用: 檢查證照持有者身份的合法性,並簽發證照,以防證照被偽造或篡改,以及對證照和金鑰進行管理

一般正常的網站都會主動出示自己的數字證照,來確保客戶端和網站伺服器之間的通訊資料是加密安全的.

Handler和Opener的使用

自定義Handler

# -*- coding:utf-8 -*-

import urllib.request

# 構建一個HTTPHandler處理器物件,支援處理HTTP的請求
# http_hander = urllib.request.HTTPHandler()
http_hander = urllib.request.HTTPHandler(debuglevel=1)

# 呼叫build_opener()方法構建一個自定義的opener物件,引數是構建的處理器物件
opener = urllib.request.build_opener(http_hander)

req = urllib.request.Request(`http://www.baidu.com`)

res = opener.open(req)

print(res.read().decode())

開放代理和私密代理

高匿:無法拿到真正的物理ip,只能獲取代理伺服器ip
透明:能看到代理伺服器ip,也可以看到物理ip地址
快代理
西刺免費代理

使用代理IP,這是爬蟲/反爬蟲的第二大招,通常也是最好用的。
很多網站會檢測某一段時間某個IP的訪問次數(通過流量統計,系統日誌等),如果訪問次數多的不像正常人,它會禁止這個IP的訪問。
可以設定一些代理伺服器,每隔一段時間換一個代理,就算IP被禁止,依然可以換個IP繼續爬取。

# -*- coding:utf-8 -*-

import urllib.request


# 代理開關,是否啟用代理
proxyswitch = True

# 公開代理
proxy_ip = {
  `http`: `123.57.217.208:3128`
}

# 私密代理 授權的賬號密碼
# proxy_ip_auth = {
#   `http`: `user:passwd@ip:prot`
# }

# 構建一個handler物件,引數是一個字典型別,包括代理型別和代理伺服器ip+prot
http_proxy_handler = urllib.request.ProxyHandler(proxy_ip)

# 構建一個沒有代理物件的處理器物件
null_proxy_headler = urllib.request.ProxyHandler({})


if proxyswitch:
  opener = urllib.request.build_opener(http_proxy_handler)
else:
  opener = urllib.request.build_opener(null_proxy_headler)

# 構建一個全域性的opener,之後的所有請求都可以用urlopen()方式傳送,也附帶Handler功能
urllib.request.install_opener(opener)
request = urllib.request.Request(`http://www.baidu.com/`)
response = urllib.request.urlopen(request)

# response = opener.open(request)

print(response.read().decode())

ProxyBasicAuthHandler(代理授權驗證):

#conding=utf-8

import urllib.request


user = ``
passwd = ``
proxyserver = ``

# 構建一個密碼管理物件,用來儲存需要處理的使用者名稱和密碼
passwdmgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()

# 新增賬戶資訊,第一個引數realm是與遠端伺服器相關的域資訊,一般都是寫None,後面三個引數分別是 代理伺服器、使用者名稱、密碼
passwdmgr.add_password(None, proxyserver, user, passwd)

# 構建一個代理基礎使用者名稱/密碼驗證的ProxyBasicAuthHandler處理器物件,引數是建立的密碼管理物件
proxy_auth_handler = urllib.request.ProxyDigestAuthHandler(passwdmgr)

# 通過 build_opener()方法使用這些代理Handler物件,建立自定義opener物件,引數包括構建的 proxy_handler 和 proxyauth_handler
opener = urllib.request.build_opener(proxy_auth_handler)

request = urllib.request.Request(`http://www.baidu.com/`)
response = opener.open(request)

print(response.read().decode())

Cookie

Cookie 是指某些網站伺服器為了辨別使用者身份和進行Session跟蹤,而儲存在使用者瀏覽器上的文字檔案,Cookie可以保持登入資訊到使用者下次與伺服器的會話。

HTTP是無狀態的面向連線的協議, 為了保持連線狀態, 引入了Cookie機制 Cookie是http訊息頭中的一種屬性

Cookie名字(Name)
Cookie的值(Value)
Cookie的過期時間(Expires/Max-Age)
Cookie作用路徑(Path)
Cookie所在域名(Domain),
使用Cookie進行安全連線(Secure)。

前兩個引數是Cookie應用的必要條件,另外,還包括Cookie大小(Size,不同瀏覽器對Cookie個數及大小限制是有差異的)。

Cookie由變數名和值組成,根據 Netscape公司的規定,Cookie格式如下:

Set-Cookie: NAME=VALUE;Expires=DATE;Path=PATH;Domain=DOMAIN_NAME;SECURE

Python處理Cookie,一般是通過cookielib模組和urllib2模組的HTTPCookieProcessor處理器類一起使用。

cookielib模組:主要作用是提供用於儲存cookie的物件

HTTPCookieProcessor處理器:主要作用是處理這些cookie物件,並構建handler物件。

cookielib 庫

該模組主要的物件有CookieJarFileCookieJarMozillaCookieJarLWPCookieJar

一般情況下,只用CookieJar(),如果需要和本地檔案互動,就需要使用MozillaCookjar()LWPCookieJar()

獲取Cookie,並儲存到CookieJar()物件中:

#!/usr/local/bin/python

import urllib.request
import http.cookiejar

cookiejar = http.cookiejar.CookieJar()

http_handler = urllib.request.HTTPCookieProcessor(cookiejar)

opener = urllib.request.build_opener(http_handler)

opener.open(`http://www.baidu.com`)

cook_str = ``
for item in cookiejar:
    cook_str = cook_str + item.name + `=` + item.value + `;`

print(cook_str[:-1])

# BAIDUID=5DB0FC0C0DC9692BB8EE6EDC93A2EDEA:FG=1;BIDUPSID=5DB0FC0C0DC9692BB8EE6EDC93A2EDEA;H_PS_PSSID=1468_26259_21099_26350_26580;PSTM=1528615563;BDSVRTM=0;BD_HOME=0

訪問網站獲得cookie,並把獲得的cookie儲存在cookie檔案中:

#!/usr/local/bin/python

import http.cookiejar
import urllib.request

filename = `cookie.txt`

# 宣告一個MozillaCookieJar(有save實現)物件例項來儲存cookie,之後寫入檔案
cookiejar = http.cookiejar.MozillaCookieJar(filename)

handler = urllib.request.HTTPCookieProcessor(cookiejar)
opener = urllib.request.build_opener(handler)
req = opener.open(`http://www.baidu.com`)

# 儲存cookie到本地檔案
cookiejar.save()

print(1)

非結構化資料和結構化資料

實際上爬蟲一共就四個主要步驟:

  • 明確目標 (要知道準備在哪個範圍或者網站去搜尋)
  • 爬 (將所有的網站的內容全部爬下來)
  • 取 (去掉對沒用處的資料)
  • 處理資料(按照想要的方式儲存和使用)

re模組

pattern = re.compile(regExp)

pattern.match(): 從起始位置開始查詢,返回第一個符合規則的,只匹配一次。
pattern.search(): 從任意位置開始查詢,返回第一個符合規則的,只匹配一次。
pattern.findall(): 所有的全部匹配,返回列表
pattern.split(): 分割字串,返回列表
pattern.sub(): 替換
rs.I 忽略大小寫
re.S 全文匹配

match(str, begin, end):

>>> pattern = re.compile(r`([a-z]+) ([a-z]+)`, re.I)
>>> m = pattern.match(`hello world hello python`)
>>> m.group()
`hello world`
>>> m.group(1)
`hello`
>>> m.group(2)
`world`

findall(str, begin, end):

>>> pattern = re.compile(r`d+`)
>>> pattern.findall(`hello world 123 456 789`)
[`123`, `456`, `789`]
>>> 

split(str, count):

>>> pattern = re.compile(r`[sd;]+`)
>>> pattern.split(`a ba;m; a  `)
[`a`, `bx07`, `m`, `a`, ``]
>>> 
>>> pattern = re.compile(`[sd;]+`)
>>> pattern.split(r`a ba;m; a  `)
[`a`, `b\a`, `m`, `a`, ``]
>>> 

sub():

>>> pattern = re.compile(r`(w+)(w+)`)
>>> strs = `hello 123, world 456`
>>> pattern.sub(`hello world`, strs)
`hello world hello world, hello world hello world`
>>>

xpath

chrome外掛:XPath Helper
XPath (XML Path Language) 是一門在 XML 文件中查詢資訊的語言,可用來在XML文件中對元素和屬性進行遍歷。

lxml庫:
lxml是 一個HTML/XML的解析器,主要的功能是如何解析和提取HTML/XML資料。

獲取屬性:@src, @title, @class
獲取內容: /text()
模糊查詢: contains(@id, `模糊字串`)

xpath匹配規則:

//div[@class="pic imgcover"]/img/@src

xpath模糊匹配:

//div[contains(@id, `qiushi_tag`)]

獲取某個網站的圖片:

#conding=utf-8

import urllib.request
import urllib.parse
from lxml import etree # 我乃河北,姓氏顏良

class getQdailyImg:
    def __init__(self, url):
        self.url = url

    def loadPage(self):
        print(`正在下載...`)
        headres = {
            `User-Agent`: `ie 的user-Agent`
        }
        req = urllib.request.Request(self.url, headers=headres)
        html = urllib.request.urlopen(req).read().decode()
        xmlDom = etree.HTML(html)
        linkList = xmlDom.xpath(`//div[@class="pic imgcover"]/img/@src`)
        print(linkList)
        self.writePage(linkList)

    def writePage(self, data):
        for item in data:
            with open(`img.txt`, `w`) as f:
                f.write(item)

if __name__ == `__main__`:
    qdI = getQdailyImg(`http://www.qdaily.com/`)
    qdI.loadPage()

# -*- coding:utf-8 -*-

import urllib.request
import json
from lxml import etree

url = `http://www.qiushibaike.com/8hr/page/1`
headers = {
    `user-agent`: `Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; GTB7.0)`
}

req = urllib.request.Request(url, headers=headers)
html = urllib.request.urlopen(req).read()
text = etree.HTML(html)

# 作為根目錄節點
node_list = text.xpath(`//div[contains(@id, "qiushi_tag")]`)

items = {}
for node in node_list:
    username = node.xpath(`./div[@class="author clearfix"]//h2/text()`)[0]

    image = node.xpath(`.//div[@class="thumb"]//@src`)

    content = node.xpath(`.//div[@class="content"]/span`)[0].text

    zan = node.xpath(`.//i`)[0].text

    comment = node.xpath(`.//i`)[1].text

    items = {
        `username`: username,
        `image`: image,
        `content`: content,
        `zan`: zan,
        `comment`: comment
    }

    with open(`qiushi.json`, `a`) as f:
        f.write(json.dumps(items, ensure_ascii=False) + `
`)

print(`ok`)

BeautifulSoup

Beautiful Soup也是一個HTML/XML的解析器,主要的功能也是如何解析和提取 HTML/XML資料。

BeautifulSoup用來解析HTML比較簡單,API非常人性化,支援CSS選擇器、Python標準庫中的HTML解析器,也支援lxml的 XML解析器。

pip install bs4
beautifulsoup4文件

  • tag: BeautifulSoup(html).div
  • attrs: BeautifulSoup(html).div.nameBeautifulSoup(html).div.attres
  • content: BeautifulSoup(html).span.string
#conding=utf-8

from bs4 import BeautifulSoup
import requests
import time

def captchaMethod(captcha_data):
    with open(`captcha.jpg`, `wb`) as f:
        f.write(captcha_data)
    return input(`請輸入驗證碼:`)     

def getLoginZhihu():
    # 構建Session物件,儲存cookie值
    sess = requests.Session()

    headers = {
        `user-agent`: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36 QQBrowser/4.3.4986.400`
    }

    html = sess.post(`https://www.zhihu.com/#sigin`, headers=headers).text
    bs = BeautifulSoup(html, `lxml`)
    _xsrf = bs.find(`input`, attrs={`name`: `_xsrf`}).get(`value`)

    captcha_url = `https://www.zhihu.com/captcha.gif?r=%d&type=login` % (time.time() * 1000)
    captcha = sess.get(captcha_url, headers=headers).content

    # 獲取驗證碼文字
    text = captchaMethod(captcha)

    data = {
        `_xsrf`: _xsrf,
        `email`: `123636374@qq.com`,
        `password`: `ALARMCHIME`,
        `captcha`: text
    }
    # 登入 獲取cookie
    res = sess.post(`https://www.zhihu.com/login/email`, data=data, headers=headers).text

    res = sess.get(`https://www.zhihu.com/people/`, headers)

if __name__ == `__main__`:
    getLoginZhihu()
    

JSON和JSONPATH

JsonJsonPath應用

  • json.loads(): 把Json格式字串解碼轉換成Python物件
  • json.dumps(): 實現python型別轉化為json字串,返回一個str物件 把一個Python物件編碼轉換成Json字串
  • json.dump(): 將Python內建型別序列化為json物件後寫入檔案
  • json.load(): 讀取檔案中json形式的字串元素 轉化成python型別
dictStr = {"city": "北京", "name": "大貓"}
print(json.dumps(dictStr, ensure_ascii=False))
# {"city": "北京", "name": "大劉"}

listStr = [{"city": "北京"}, {"name": "大劉"}]
json.dump(listStr, open("listStr.json","w"), ensure_ascii=False)

strDict = json.load(open("dictStr.json"))
print(strDict)
# {u`city`: u`u5317u4eac`, u`name`: u`u5927u5218`}

# -*- coding:utf-8 -*-

import json
import urllib.request
import jsonpath

headers = {
    `user-agent`: `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36 QQBrowser/4.3.4986.400`
}
url = `https://www.lagou.com/lbs/getAllCitySearchLabels.json`

request = urllib.request.Request(url, headers=headers)

response = urllib.request.urlopen(request)
html = response.read().decode()

unicodeStr = json.loads(html)
content = jsonpath.jsonpath(unicodeStr, `$..name`)
print(content)
array = json.dumps(content, ensure_ascii=False)

with open(`lagoucontent.json`, `w`) as f:
    f.write(array)

多執行緒爬蟲

一個程式可能包括多個執行緒,執行緒之間執行任務,必須通過加鎖方式控制它們(阻塞)
父執行緒和子執行緒都關係,只要父執行緒執行完,不管子執行緒如何,都一併結束

  1. 計算機的核心是CPU,CPU承擔了所有的計算任務
  2. 一個CPU核心一次只能執行一個任務
    多個CPU核心同時可以執行多個任務
  3. 一個CPU一次只能執行一個程式,其它程式處於非執行
  4. 程式裡包含的執行單元叫執行緒
    一個程式 可以包含 多個執行緒
  5. 一個程式的記憶體空間是共享的,每個程式裡的執行緒都可以使用這個共享空間
    一個執行緒在使用這個共享空間的時候,其它執行緒必須等待它結束
  6. 通過“鎖”實現,作用就是防止多個執行緒使用當前記憶體空間。
    先使用的執行緒會加鎖,鎖上該空間,其它執行緒就在等待。

程式:表示程式的一次執行
執行緒:CPU運算的基本排程單位

GIL: Python裡的執行通行證,而且只有唯一個。拿到通行證的執行緒才會執行

Python 的多執行緒適用於:大量密集的I/O處理 (單獨都任務,一個程式,只能執行一個任務)
Python 的多程式適用於:大量的密集平行計算

#conding=utf-8

import json
import threading
from queue import Queue

import requests
from lxml import etree

CREAWL_EXIT = False
PARSE_EXIT = False

```
    採集執行緒
```
class ThreadCrawl(threading.Thread):
    def __init__(self, threadName, pageQueue, dataQueue):
        # threading.Thread.__init__(self)
        super(ThreadCrawl, self).__init__() # 多個父類,多重繼承
        self.threadName = threadName
        self.pageQueue = pageQueue
        self.dataQueue = dataQueue
        self.headers = {
            `user-agent`: `Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; GTB7.0)`
        }

    def run(self):
        print(`start` + self.threadName)
        while not CREAWL_EXIT:
            try:
                page = self.pageQueue.get(False)
                url = `https://www.qiushibaike.com/8hr/page/` + str(page) + `/`
                res = requests.get(url, headers=self.headers).text
                self.dataQueue.put(res)
            except:
                pass
        print(`end` + self.threadName)      

```
    解析執行緒
```
class ThreadParse(threading.Thread):
    def __init__(self, threadingName, dataQueue, filename):
        super(ThreadParse, self).__init__()
        self.threadingName = threadingName
        self.dataQueue = dataQueue
        self.filename = filename

    def run(self):
        print(`start` + self.threadingName)
        while not PARSE_EXIT:
            try:
                html = self.dataQueue.get(False)
                self.parse(html)
            except:
                pass
        print(`end` + self.threadingName)


    def parse(self, html):
        text = etree.HTML(html)
        node_list = text.xpath(`//div[contains(@id, "qiushi_tag")]`)
        items = {}
        for node in node_list:
            username = node.xpath(`./div[@class="author clearfix"]//h2/text()`)[0]
            image = node.xpath(`.//div[@class="thumb"]//@src`)
            content = node.xpath(`.//div[@class="content"]/span`)[0].text
            zan = node.xpath(`.//i`)[0].text
            comment = node.xpath(`.//i`)[1].text
            items = {
                `username`: username,
                `image`: image,
                `content`: content,
                `zan`: zan,
                `comment`: comment
            }
            self.filename.write(json.dumps(items, ensure_ascii=False) + `
`)


def main():
    # 頁碼
    pageQueue = Queue(10)
    # 放入1~10的數字
    for i in range(1, 10+1):
        pageQueue.put(i)

    # 採集結果(每頁的HTML原始碼)
    dataQueue = Queue()

    filename = open(`duanzi.json`, `a`)

    crawlList = [`採集執行緒1`, `採集執行緒2`, `採集執行緒3`]

    threadcrawl = []
    for threadName in crawlList:
        thread = ThreadCrawl(threadName, pageQueue, dataQueue)
        thread.start()
        threadcrawl.append(thread)
    
    parseList = [`解析執行緒1`, `解析執行緒2`, `解析執行緒3`]
    threadparse = []
    for threadName in parseList:
        thread = ThreadParse(threadName, dataQueue, filename)
        thread.start()
        threadparse.append(thread)
    
    # 等待pageQueue佇列為空, 或者 資料佇列為空,也就是等待之前執行的操作執行完畢
    while not pageQueue.empty() or not dataQueue.empty():
        pass

    global CREAWL_EXIT
    CREAWL_EXIT = True
    print(`queue佇列為空`)

    global PARSE_EXIT
    PARSE_EXIT = True
    print(`data佇列為空`)

    for threadItem in crawlList:
        threadItem.join(``)
        print(`1`)

if __name__ == `__main__`:
    main()

自動化測試unittest模組使用和模擬使用者點選抓取資料(拿去ajax分頁資料)

# -*- coding:utf-8 -*-

import unittest
from selenium import webdriver
from bs4 import BeautifulSoup as bs

class Douyu(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.PhantomJS()

    # unittest測試方法必須有`test`字樣開頭
    def testDouyu(self):
        self.driver.get(`https://www.douyu.com/directory/all`)
        while True:
            soup = bs(self.driver.page_source, `lxml`)
            names = soup.find_all(`h3`, {`class`: `ellipsis`})
            viewNums = soup.find_all(`span`, {`class`: `dy-num fr`})
            
            for name, viewNum in zip(names, viewNums):
                print(`房間名` + name.get_text() + `; ` + `觀眾人數` + viewNum.get_text())

            # 在頁面原始碼中找到"下一頁"未隱藏的標籤,就退出迴圈
            if self.driver.page_source.find(`shark-pager-disable-next`) != -1:
                break

            # 一直點選下一頁
            self.driver.find_element_by_class_name(`shark-pager-next`).click()    

    # 測試結束執行的方法
    def tearDown(self):
        self.driver.quit()

if __name__ == `__main__`:
    unittest.main()

執行javascript語句:execute_script

#conding=utf-8

from selenium import webdriver
import time

driver = webdriver.PhantomJS(`/Users/linxingzhang/Library/Python/3.6/lib/python/site-packages/selenium/webdriver/phantomjs`)
driver.get("https://movie.douban.com/typerank?type_name=劇情&type=11&interval_id=100:90&action=")

time.sleep(3)
# 向下滾動10000畫素
js = "document.body.scrollTop=10000"
# js="var q=document.documentElement.scrollTop=10000"

# 檢視頁面快照
driver.save_screenshot("douban.png")

# 執行JS語句
driver.execute_script(js)
time.sleep(10)

# 檢視頁面快照
driver.save_screenshot("newdouban.png")

driver.quit()

投票

import datetime
import sys
import threading
import time
from random import choice  # choice() 方法返回一個列表,元組或字串的隨機項

import requests
from lxml import etree

from fake_useragent import UserAgent  # 引入隨機的UA

# 設定user-agent列表,每次請求時,隨機挑選一個user-agent
ua_list = UserAgent()


def get_ip():
    ```
        獲取代理ip
    ```
    url = `http://www.xicidaili.com/nn`
    headers = {
        `Accept`: `text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8`,
        `Host`: `www.xicidaili.com`,
        `Referer`: `http: // www.xicidaili.com/nn`,
        `User-Agent`: `Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6726.400 QQBrowser/10.2.2265.400`
    }
    ret = requests.get(url, headers=headers)
    xmlDom = etree.HTML(ret.text)

    data = xmlDom.xpath(`//table[@id="ip_list"]//tr`)
    z = []
    for tr in data:
        if tr.xpath(`td`):
            ip = tr.xpath(`td`)[1].text  # 獲取所有IP
            port = tr.xpath(`td`)[2].text  # 獲取所有埠
            z.append(ip + `:` + port)
    return z


def get_url(url, code=0, ips=[]):
    ```
        投票
        如果因為代理IP已失效造成投票失敗,則會自動換一個代理IP後繼續投票
    ```
    try:
        ip = choice(ips)
        print(ip, `ip` * 5)
    except:
        return False
    else:
        proxies = {
            `http`: ip
        }
        headers = {
            `Content-Type`: `application/x-www-form-urlencoded; charset=UTF-8`,
            `Host`: `best.zhaopin.com`,
            `Origin`: `https: // best.zhaopin.com`,
            `Referer`: `https//best.zhaopin.com/?sid=121128100&site=sou`,
            `User-Agent`: ua_list.random
        }
        print(ua_list.random, `ua_list` * 5)

    try:
        data = {`bestid`: `3713`, `score`: `5,5,5,5,5,5`, `source`: `best`}
        # 跳過證照的驗證 verify=False
        result = requests.post(url, data=data, headers=headers, proxies=proxies)
        print(result, `result` * 5)
    except requests.exceptions.ConnectionError:
        print(`ConnectionError`)
        if not ips:
            print(`ip 失效`)
            sys.exit()

        # 刪除不可用的代理IP
        if ip in ips:
            ips.remove(ip)
        # 重新請求url
        get_url(url, code=0, ips=[])
    else:
        date = datetime.datetime.now().strftime(`%H:%M:%S`)
        # result.text() 投票成功顯示1  失敗顯示0
        print(`第%s次 [%s] [%s]:投票%s (剩餘可用代理IP數:%s)` %
              (code, date, ip, result.text, len(ips)))


def main():
    url = `https://best.zhaopin.com/API/ScoreCompany.ashx`  # 投票的請求
    ips = []
    for i in range(6000):
        if i % 1000 == 0:
            ips.extend(get_ip())
            # print(`-` * 100)
            # print(ips)
        t = threading.Thread(target=get_url, args=(url, i, ips))
        t.start()
        time.sleep(1)


if __name__ == `__main__`:
    main()

Tesseract

機器識別中的文字識別

pip install pytesseract

識別圖片中的文字:

#conding=utf-8

import pytesseract
from PIL import Image

image = Image.open(`./mayday.jpg`)

text = pytesseract.image_to_string(image)

print(text)

asyncio & aiohttp

通過非同步庫aiohttp,asyncio爬取圖片

# -*- coding:utf-8 -*-
import asyncio
import os
import time

import aiohttp
import requests


class Spider(object):
    def __init__(self):
        self.num = 1
        if `load-img` not in os.listdir(`.`):
            os.mkdir(`load-img`)
        self.path = os.path.join(os.path.abspath(`.`), `load-img`)
        os.chdir(self.path)  # 進入檔案下載路徑

    def run(self):
        start = time.time()
        for x in range(1, 101): # 爬取100張圖片,更改數值,爬取更多圖片
            links = self.__get_img_links(x)
            tasks = [asyncio.ensure_future(self.__download_img(
                (link[`id`], link[`links`][`download`])
            )) for link in links]
            loop = asyncio.get_event_loop()
            loop.run_until_complete(asyncio.wait(tasks))
            # if self.num >= 10:
            #     break
        end = time.time()
        print(`run %s s` % (end - start))

    def __get_img_links(self, page):
        url = `https://unsplash.com/napi/photos`
        data = {
            `page`: page,
            `per_page`: 12,
            `order_by`: `latest`
        }
        response = requests.get(url, params=data)
        if response.status_code == 200:
            return response.json()
        else:
            print(`request %s` % response.status_code)

    async def __download_img(self, img):
        content = await self.__get_content(img[1])
        with open(img[0] + `.jpg`, `wb`) as f:
            f.write(content)
        print(`load %s page success` % self.num)
        self.num += 1

    async def __get_content(self, link):
        async with aiohttp.ClientSession() as session:
            response = await session.get(link)
            content = await response.read()
            return content


def main():
    spider = Spider()
    spider.run()


if __name__ == `__main__`:
    main()

相關文章