一.了解網絡爬蟲
1.1爬蟲的定義
網絡爬蟲又稱為網頁蜘蛛、網絡機器人。網絡爬蟲是一種按照一定的規則自動的抓取網絡資訊的程式或者腳本。通俗的說,就是根據一定的算法實作程式設計開發,主要通過URL實作資料的抓取和挖掘。
1.2爬蟲的類型
根據系統結構和開發技術大緻可分為4種類型:
(1)通用網絡爬蟲,又稱為全網爬蟲,常見的有百度,Google等。
(2)聚焦網絡爬蟲,又稱主題網絡爬蟲,是選擇性的爬行根據需求的主題相關頁面的網絡爬蟲。
(3)增量式網絡爬蟲。是指對已下載下傳網頁采取增量式更新和隻爬行新産生或者已經發生變化的網頁的爬蟲,它能夠在一定程度上保證所爬行的頁面盡可能是新的頁面。隻會在需要的時候爬行新産生或發生更新的頁面,并不重新下載下傳沒有發生變化的頁面,這類爬蟲在實際中不太普及。
(4)深層網絡爬蟲。是大部分内容不能通過靜态URL擷取的,隐藏在表單後的,隻有使用者送出一些關鍵詞才能獲得的網絡頁面。
這四類大緻又可以分為兩類,通用爬蟲和聚焦爬蟲。聚焦網絡爬蟲,增量式網絡爬蟲和深層網絡爬蟲可歸為一類,因為他們是定向爬蟲資料。而通用爬蟲在網絡上稱為搜尋引擎。
爬蟲的設計思路:
(1) 明确需要爬取的網頁的URL位址;
(2)通過http請求來擷取對應的HTML頁面;
(3)提取HTML的内容。若是有用的資料,就儲存起來;若是繼續爬取的頁面,就重新指定第(2)步。
二.爬蟲開發基礎
2.1 HTTP與HTTPS
http是一個簡單的請求-響應協定,它通常運作在TCP之上。它指定了用戶端可能發送給伺服器什麼樣的消息以及得到什麼樣的響應。用戶端是終端使用者,伺服器端是網站。通常使用web浏覽器,網絡爬蟲或者其他工具,用戶端會發起一個到伺服器上指定的http請求。這個用戶端就叫做使用者代理(User Agent)。一旦收到請求,伺服器會發回一個狀态行(如“http/1.1 200 OK”)和 響應的 消息,其中消息的内容可能是請求的檔案,錯誤消息或者其他消息。
http協定傳輸的資料都是未加密的,是以使用http協定傳輸隐私資訊非常不安全。
https協定傳輸的資料都是加密的。在傳輸資料之前需要用戶端與服務端之間進行一次握手,在握手過程中将确立雙方加密傳輸資料的密碼資訊。
2.2 請求頭
請求頭描述用戶端向伺服器發送請求時使用的協定類型,所使用的編碼以及發送内容的長度等。檢測請求頭是常見的反爬蟲政策。因為伺服器會對請求頭做一次檢測來判斷此次請求是人為的還是非人為的。是以我們在每次發送請求時都添加上請求頭。
請求頭的參數如下:
(1)Accept:浏覽器可以接收的類型
(2)Accept-Charset:編碼類型
(3)Accept-Encoding:可以接收壓縮編碼類型
(4)Accept-Language:可以接收的語言和國家類型
(5)Host:請求的主機位址和端口
(6)Referer:請求來自于那個頁面的URL
(7)User-Agent:浏覽器相關資訊
(8)Cookie:浏覽器暫存伺服器發送的資訊
(9)Connection:http請求版本的特點
(10)Date:請求網站的時間
2.3 cookies
它是指某些網站為了辯護使用者身份,進行session跟蹤而存儲在使用者本地終端上的資料。一個cookies就是存儲在使用者主機浏覽器 的文本檔案。
伺服器可以利用cookies包含的資訊判斷在http傳輸中的狀态。
2.4 JSON
JSON(JavaScript Object Notation, JS 對象簡譜) 是一種輕量級的資料交換格式。它基于ECMAScript (歐洲計算機協會制定的js規範)的一個子集,采用完全獨立于程式設計語言的文本格式來存儲和表示資料。簡潔和清晰的層次結構使得 JSON 成為理想的資料交換語言。 易于人閱讀和編寫,同時也易于機器解析和生成,并有效地提升網絡傳輸效率。
例如:
{"name": "John Doe", "age": 18, "address": {"country" : "china", "zip-code": "10000"}}
2.5 JavaScript
JavaScript(簡稱“JS”) 是一種具有函數優先的輕量級,解釋型或即時編譯型的進階程式設計語言。是一種解釋性腳本語言(代碼不進行預編譯), 主要用來向HTML(标準通用标記語言下的一個應用)頁面添加互動行為。
JavaScript還能根據使用者觸發某些事件對使用者的操作進行加工處理。要在爬蟲實作一些功能,就要分析JS如何執行整個使用者登入過程。
2.6 Ajax
Ajax 不是一種新的程式設計語言,而是一種用于建立更好更快以及互動性更強的Web應用程式的技術。使用 JavaScript 向伺服器提出請求并處理響應而不阻塞使用者核心對象XMLHttpRequest。通過這個對象,您的 JavaScript 可在不重載頁面的情況與 Web 伺服器交換資料,即在不需要重新整理頁面的情況下,就可以産生局部重新整理的效果。
Ajax 在浏覽器與 Web 伺服器之間使用異步資料傳輸(HTTP 請求),這樣就可使網頁從伺服器請求少量的資訊,而不是整個頁面。
判斷網頁資料是否使用ajax的方法:觸發事件之後,判斷網頁是否發生重新整理狀态。若網頁沒有發生重新整理,資料就自動生成,說明資料的加載是通過ajax生成并渲染到網頁上的。反之,資料是通過伺服器背景生成并加載的。
三.Fiddler抓包工具
Fiddler是一個http協定調試代理工具,它能夠記錄并檢查所有你的電腦和網際網路之間的http通訊,設定斷點,檢視所有的“進出”Fiddler的資料(指cookie,html,js,css等檔案)。
3.1 fiddler安裝配置
在官網網站下載下傳(https://www.telerik.com/download/fiddler),界面如下圖所示:

配置fiddler,使其能夠抓取https請求資訊:
(1)打開菜單-tools-fidder options-https。
(2)勾選https中的選項,然後點選actions-trust root certificate,完成整數驗證。
3.2 fiddler抓取手機應用
fiddler可通過同一無線網絡實作對手機應用的抓包,手機抓包主要通過遠端連接配接實作手機和fiddler通信。
實作fiddler抓取手機應用的步驟如下:
(1)配置fiddler遠端連接配接模式。打開菜單欄:tools--fiddler options--connections,把allow remote computers to connect勾選上。
(2)在手機端進行參數配置。要連接配接在同一個IP位址上(cmd中輸入ipconfig檢視IP位址)
(3)在手機浏覽器中輸入電腦IP位址和fiddler端口,點選确認後跳轉到證書下載下傳頁面,點選下載下傳fiddlerroot certificate.
(4)證書檔案以cer為字尾名,完成證書安裝後,進入手機目前連接配接WiFi詳情,設定代理IP,主機名為電腦IP位址,端口為fiddler配置的端口。
四.開始爬蟲
4.1 urllib子產品
在python3中不存在urllib2子產品,同一為urllib。urllib子產品有如下四個子子產品:
1 urllib.request:用來打開和讀取URL。2 urllib.error:包含了urllib.request産生的異常。3 urllib.parse:用來解析和處理URL。4 urllib.robotparse:用于解析robots.txt檔案。
urllib的方法及使用:
1 urllib.urlopen(url[, data[, proxies[,timeout[, context]]]]) 功能說明:urllib是用于通路URL的唯一方法
參數解釋:
url :一個完整的遠端資源路徑,一般是一個網站。 data:預設值為none。若參數data為none,則代表請求方式為get,反之請求方式為post,發送post請求。參數data以字典形式存儲資料,并将參數data由字典類型轉換成位元組類型才能完成post請求。 proxies : 設定代理 timeout:逾時設定 context:描述各種SSL選項的執行個體。
2 urllib.request.Request(url,data=none,headers={},method=none)
功能說明:聲明一個request對象,該對象可定義header等請求資訊
參數解釋:
headers:設定request請求頭資訊
method:設定請求方式,主要是get和post
"""urllib的使用"""
import urllib.request# 向指定URL發送請求,擷取響應。response = urllib.request.urlopen('http://httpbin.org/anything')# 擷取響應内容content = response.read().decode('utf-8')print(content)print(type(response))# 響應碼print(response.status)# 響應頭資訊print(response.headers)
# 導入urllibimport urllib.request
url = 'https://movie.douban.com/'# 自定義請求頭headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)'
'Chrome/45.0.2454.85 Safari/537.36 115Browser/6.0.3', 'Referer': 'https://movie.douban.com/', 'Connection': 'keep-alive'}# 設定request的請求頭req = urllib.request.Request(url, headers=headers)# 使用urlopen打開reqhtml = urllib.request.urlopen(req).read().decode('utf-8')# 寫入檔案f = open('code2.txt', 'w', encoding='utf8')
f.write(html)
f.close()
4.2 requests子產品
requests子產品是在urllib的基礎上做了封裝,具備urllib的全部功能,讓使用者更加友善的使用。
安裝:pip install requests
4.2.1 送出請求
有兩種方式:requests.get()和requests.post()方法
1 requests.get(url, params=none, **kwargs)2 requests.post(url, data=none, json=none, **kwargs)
requests提供如下方法擷取響應内容:
1 r.status_code:響應狀态碼(200表示通路成功,4**表示失敗) 2 r.raw:原始響應體,使用r.raw.read()讀取 3 r.content:位元組方式的響應體,需要進行解碼 4 r.text:字元串方式的響應體,會自動根據響應頭部的字元編碼進行解碼 5 r.headers:以字典對象存儲伺服器響應頭,若鍵不存在,則傳回none 6 r.json():requests中内置的json解碼器 7 r.raise_for_status():請求失敗(非200響應),抛出異常 8 r.url:擷取請求連結 9 r.cookies:擷取請求後的cookies10 r.encoding:擷取編碼格式
"""使用requests發送請求和攜帶參數"""import requests
r = requests.get('https://httpbin.org/get') # 發送get請求print(r.text)# 發送post請求,并帶參數r = requests.get('https://httpbin.org/get', params={'key1': 'value1', 'key2': 'value2'})print(r.text)# 發送post請求,并傳遞參數r = requests.post('https://httpbin.org/post', data={'key': 'value'})print(r.text)# 其他HTTP請求類型:PUT,DELETE,HEAD和OPTIONSr = requests.put('https://httpbin.org/put', data={'key': 'value'})print(r.text)
r = requests.delete('https://httpbin.org/delete')print(r.text)
r = requests.head('https://httpbin.org/get')print(r.text)
r = requests.options('https://httpbin.org/get')print(r.text)
4.2.2 複雜的請求方式
(1)添加請求頭import requests
headers = { 'content-type': 'application/json', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0'}
requests.get("https://www.baidu.com/", headers=headers)
(2)使用代理IPimport requests
proxies = { "http": "http://10.10.1.10:3128", "https": "http://10.10.1.10:1080",
}
requests.get("https://www.baidu.com/", proxies=proxies)
(3)證書驗證import requests
url = 'https://kyfw.12306.cn/otn/leftTicket/init'# 關閉證書驗證r = requests.get(url, verify=False)print(r.status_code)# 開啟證書驗證# r = requests.get(url, verify=True)# 設定證書所在路徑# r = requests.get(url, verify= '/path/to/certfile')(4)逾時設定
requests.get("https://www.baidu.com/", timeout=2)
requests.post("https://www.baidu.com/", timeout=2)
(5)使用cookies
import requests
temp_cookies='JSESSIONID_GDS=y4p7osFr_IYV5Udyd6c1drWE8MeTpQn0Y58Tg8cCONVP020y2N!450649273;name=value'cookies_dict = {}for i in temp_cookies.split(';'):
value = i.split('=')
cookies_dict [value[0]] = value[1]
r = requests.get(url, cookies=cookies)print(r.text)
4.2.3 錯誤和異常
若出現網絡問題,則請求将引發connectionerror異常。
若http請求傳回不成功的狀态碼,則将會引發httperror異常。
若請求逾時,則會引起逾時異常。
若請求超過配置的最大重定向數,則會引發TooManyRedirects異常。
請求顯示引發的所有異常都繼承自requests.exceptions.RequestException。
4.3 re子產品
正規表達式是對字元串操作的一種邏輯公式,就是用事先定義好的一些特定字元、及這些特定字元的組合,組成一個“規則字元串”,這個“規則字元串”用來表達對字元串的一種過濾邏輯。
正規表達式是對字元串(包括普通字元(例如,a 到 z 之間的字母)和特殊字元(稱為“元字元”))操作的一種邏輯公式,就是用事先定義好的一些特定字元、及這些特定字元的組合,組成一個“規則字元串”,這個“規則字元串”用來表達對字元串的一種過濾邏輯。正規表達式是一種文本模式,該模式描述在搜尋文本時要比對的一個或多個字元串。
文法請參考:https://www.runoob.com/regexp/regexp-tutorial.html
4.3.1 子產品内容
1. re.match(pattern, string[, flags])
這個方法将會從string(我們要比對的字元串)的開頭開始,嘗試比對pattern,一直向後比對,如果遇到無法比對的字元,立即傳回None,如果比對未結束已經到達string的末尾,也會傳回None。兩個結果均表示比對失敗,否則比對pattern成功,同時比對終止,不再對string向後比對。
"""match方法"""import re# match在起始位置比對ret = re.match('www', 'www.example.com')print(type(ret))# 擷取比對的内容print(ret.group())# 擷取比對内容在原字元串裡的下标print(ret.span())# 比對不成功,傳回Noneprint(re.match('com', 'www.example.com'))
line = "Cats are smarter than dogs"matchObj = re.match(r'(.*) are (.*?) .*', line, re.M | re.I)if matchObj: print("matchObj.group() : ", matchObj.group()) # 擷取第一組的内容
print("matchObj.group(1) : ", matchObj.group(1)) print("matchObj.group(2) : ", matchObj.group(2))else: print("No match!!")
參數說明:
• pattern:比對的正規表達式
• string:要比對的字元串
• flags:标志位,用于控制正規表達式的比對方式,如是否區分大小寫,是否多行比對等。
flags參數可選值:
• re.I: 忽略大小寫(括号内是完整寫法,下同)
• re.M: 多行模式,改變'^'和'$'的行為(參見上圖)
• re.S: 點任意比對模式,改變'.'的行為
• re.L: 使預定字元類 \w \W \b \B \s \S 取決于目前區域設定
• re.U: 使預定字元類 \w \W \b \B \s \S \d \D 取決于unicode定義的字元屬性
• re.X: 詳細模式。這個模式下正規表達式可以是多行,忽略空白字元,并可以加入注釋。
2. re.search(pattern, string[, flags])
search方法與match方法極其類似,差別在于match()函數隻檢測re是不是在string的開始位置比對,search()會掃描整個string查找比對,match()隻有在0位置比對成功的話才有傳回,如果不是開始位置比對成功的話,match()就傳回None。同樣,search方法的傳回對象同樣match()傳回對象的方法和屬性。
"""search方法"""import re# search查找第一次出現ret = re.search('www', 'www.aaa.com www.bbb.com')print(type(ret))print(ret.group())print(ret.span())#比對不成功,傳回Noneprint(re.search('cn', 'www.aaa.com www.bbb.com'))
line = "Cats are smarter than dogs";
searchObj = re.search(r'(.*) are (.*?) .*', line, re.M | re.I)if searchObj: print("searchObj.group() : ", searchObj.group()) print("searchObj.group(1) : ", searchObj.group(1)) print("searchObj.group(2) : ", searchObj.group(2))else: print("Nothing found!!")
3. re.findall(pattern, string[, flags])
搜尋string,以清單形式傳回全部能比對的子串。
"""findall方法"""import re# 查找數字result1 = re.findall(r'\d+','baidu 123 google 456')
result2 = re.findall(r'\d+','baidu88oob123google456')print(result1)print(result2)
4. re.finditer(pattern, string[, flags])
搜尋string,傳回一個順序通路每一個比對結果(Match對象)的疊代器。
"""finditer方法"""import re#傳回一個疊代器,可以循環通路,每次擷取一個Match對象it = re.finditer(r"\d+", "12a32bc43jf3")for match in it: print(match.group())
5. re.split(pattern, string[, maxsplit])
按照能夠比對的子串将string分割後傳回清單。maxsplit用于指定最大分割次數,不指定将全部分割。
6. re.sub(pattern, repl, string, count=0, flags=0)
"""sub方法"""import re
phone = "2004-959-559 # 這是一個國外電話号碼"# 删除字元串中的 Python注釋num = re.sub(r'#.*$', "", phone)print("電話号碼是: ", num)# 删除非數字(-)的字元串num = re.sub(r'\D', "", phone)print("電話号碼是 : ", num)# 将比對的數字乘以 2def double(matched):
value = int(matched.group('value')) return str(value * 2)
s = 'A23G4HFD567'print(re.sub('(?P\d+)', double, s))
參數說明:
1 repl:用于替換的字元串2 string:要被替換的字元串3 count:替換的次數
7. re.subn(pattern, repl, string[, count])
與sub()函數一緻,傳回結果是一個元組。
8. re.compile(pattern[, flags])
該函數用于編譯正規表達式生成一個正規表達式(pattern)對象,供match()和search()等函數使用。
"""compile方法"""import re# 用于比對至少一個數字pattern = re.compile(r'\d+')# 查找頭部,沒有比對m = pattern.match('one12twothree34four')print(m)# 從'e'的位置開始比對,沒有比對m = pattern.match('one12twothree34four', 2, 10)print(m)# 從'1'的位置開始比對,正好比對,傳回一個 Match 對象m = pattern.match('one12twothree34four', 3, 10)print(m)# 可省略 0print(m.group(0))
4.4BeautifulSoup4子產品
4.4.1 簡介
Beautiful Soup,有了它我們可以很友善地提取出 HTML 或 XML 标簽中的内容。BeautifulSoup是一個高效的網頁解析庫,可以從 HTML 或 XML 檔案中提取資料。
beautifulsoup支援不同的解析器,比如,對HTML解析,對XML解析,對HTML5解析。一般情況下,我們用的比較多的是 lxml 解析器。
4.4.2 安裝
1 pip install beautifulsoup4
使用時導入:
1 from bs4 import BeautifulSoup
4.4.3 BeautifulSoup庫解析器
解析器 | 使用方法 | 條件 |
bs4的HTML解析器 | BeautifulSoup(html,'html.parser') | 安裝bs4庫 |
lxml的HTML解析器 | BeautifulSoup(html,'lxml') | pip install lxml |
lxml的XML解析器 | BeautifulSoup(html,'xml') | |
html5lib的解析器 | BeautifulSoup(html,'htmlslib') | pip install html5lib |
4.4.4 使用
"""将字元串解析為HTML文檔解析"""from bs4 import BeautifulSoup
html = """The Dormouse's storyThe Dormouse's storyOnce upon a time there were three little sisters; and their names were,Lacie andTillie;
and they lived at the bottom of a well...."""# 建立BeautifulSoup對象解析html,并使用lxml作為xml解析器soup = BeautifulSoup(html, 'lxml')# 格式化輸出soup對象的内容print(soup.prettify())
例一:
"""bs4執行個體測試"""from bs4 import BeautifulSoupimport re
html = """"""# 建立對象soup = BeautifulSoup(html, 'lxml')# 擷取Tag對象# 查找的是在所有内容中的第一個符合要求的标簽print(soup.title)print(type(soup.title))print(soup.p)# 标簽的名字print(soup.p.name)# 标簽的屬性,可擷取,也可以設定print(soup.p.attrs)print(soup.p.attrs['class'])# 文本print(soup.p.string)# content屬性得到子節點,清單類型print(soup.body.contents)# children屬性屬性得到子節點,可疊代對象print(soup.body.children)# descendants屬性屬性得到子孫節點,可疊代對象print(soup.body.descendants)# find_all 查找所有符合要求的 name是按照标簽名字查找print(soup.find_all(name='b'))print(soup.find_all(name=['a', 'b']))print(soup.find_all(name=re.compile("^b")))# find_all 查找所有符合要求的 可以按照屬性查找,比如這裡的id,class# 因為class是關鍵字,是以使用class_代替classprint(soup.find_all(id='link2'))print(soup.find_all(class_='sister'))# select 查找所有符合要求的 支援選擇器print(soup.select('title'))print(soup.select('.sister'))print(soup.select('#link1'))print(soup.select('p #link1'))print(soup.select('a[class="sister"]'))# 擷取内容print(soup.select('title')[0].get_text())
例二:
>>> from bs4 import BeautifulSoup
>>> import requests
>>> r = requests.get("http://python123.io/ws/demo.html")
>>> demo = r.text
>>> demo
'This is a python demo page\r\n\r\nThe demo python introduces several python courses.\r\nPython is a wonderful general-purpose programming language. You can learn Python from novice to professional by tracking the following courses:\r\nBasic Python and Advanced Python.\r\n'
>>> soup = BeautifulSoup(demo,"html.parser")
>>> soup.title #擷取标題This is a python demo page>>> soup.a #擷取a标簽Basic Python>>> soup.title.string
'This is a python demo page'
>>> soup.prettify() #輸出html标準格式内容
'\n\n\n This is a python demo page\n\n\n\n\n \n The demo python introduces several python courses.\n \n\n\n Python is a wonderful general-purpose programming language. You can learn Python from novice to professional by tracking the following courses:\n \n Basic Python\n \n and\n \n Advanced Python\n \n .\n\n\n'
>>> soup.a.name #每個都有自己的名字,通過.name擷取
'a'
>>> soup.p.name
'p'
>>> tag = soup.a
>>> tag.attrs
{'href': 'http://www.icourse163.org/course/BIT-268001', 'class': ['py1'], 'id': 'link1'}
>>> tag.attrs['class']
['py1']
>>> tag.attrs['href']
'http://www.icourse163.org/course/BIT-268001'
>>> type(tag.attrs)>>> type(tag)
五.更多資料提取的方式
5.1 XPath和lxml
5.1.1 xml
xml被用來傳輸和存儲資料。參考:https://www.runoob.com/xml/xml-tutorial.html
5.1.2 xpath
5.1.3 lxml
# lxml安裝:pip install lxml# 使用(1)"""将字元串解析為HTML文檔"""from lxml import etree
text='''firstitemseconditemthirditemfourthitemfifthitem'''#利用etree.HTML,将字元串解析為HTML文檔html=etree.HTML(text)#按字元串序列化HTML文檔result=etree.tostring(html).decode('utf-8')print(result)
(2)"""讀檔案"""from lxml import etree# 讀取外部檔案hello.htmlhtml = etree.parse('./data/hello.html')# pretty_print=True表示格式化,比如左對齊和換行result = etree.tostring(html, pretty_print=True).decode('utf-8')print(result)