前序
本來說好隨緣更新的,因為想去寫測試相關的文件,但想想,既然第一篇爬蟲文章都發布吧,就繼續完善吧~
上一章回顧:JB的Python之旅-爬蟲篇--urllib和Beautiful Soup
看回之前寫的爬蟲計劃:
關於後續爬蟲的計劃:
目前還處於初級的定向指令碼編寫,本文內容主要介紹requests庫跟Scrapy框架;
計劃下一篇是Selenium,再下一篇是分散式爬蟲,後面加點實戰,再看看怎麼更新吧~
本文介紹:
上一篇主要介紹了urllib 跟 BeautifuSoap,練手的專案有小說網站及百度圖片的爬取,對於日常工作來說,感覺是夠了~
但只要有瞭解過爬蟲,肯定聽過requests跟Scrapy,這兩個又是什麼?
簡單介紹,requests是一個第三方庫,正如其名,就是處理請求相關內容,比起Python自帶的urllib庫,用起來相對的方便;
Scrapy呢,度娘給的介紹是:
Python開發的一個快速、高層次的螢幕抓取和web抓取框架,用於抓取web站點並從頁面中提取結構化的資料;
Scrapy吸引人的地方在於它是一個框架,任何人都可以根據需求方便的修改。它也提供了多種型別爬蟲的基類,如BaseSpider、sitemap爬蟲等;
複製程式碼
那,request是跟scrapy區別在哪裡?
從使用層面來說,單純爬取一個網頁的話,兩者區別不大,本質是一樣的,但scrapy是一個專業爬蟲框架,假如需要派去1W個網站的時候,差異就出現了,需要做併發,監控儲存,scrapy是可以做,但request這塊貌似做不到;
從程式碼層面,scrapy裡面可以使用requests的內容,會有較多二次封裝,在使用上更加方便~
更多的內容就度娘吧,這裡不展開了,留個大概印象就行了~
另外,本文還會使用另外一個網頁解析方法--xpath,上篇文章我們學會了用beautifulsoup,那為什麼要用xpath?
beautifulsoup是先把整個網頁結構解析,然後再查詢相關內容,而xpath是直接查詢,不需要做額外的解析操作,
因此可以得出,在效能效率上,xpath是灰常灰常的快的~
requests庫
本小節主要介紹下requests的庫常見使用,以及會介紹一個內容實戰~
1.簡介
Requests 是用Python語言編寫,基於 urllib,採用 Apache2 Licensed 開源協議的 HTTP 庫。
它比 urllib 更加方便,可以節約我們大量的工作,
看了下官方文件,有以下特性:
Keep-Alive & 連線池
國際化域名和 URL
帶持久 Cookie 的會話
瀏覽器式的 SSL 認證
自動內容解碼
基本/摘要式的身份認證
優雅的 key/value Cookie
自動解壓
Unicode 響應體
HTTP(S) 代理支援
檔案分塊上傳
流下載
連線超時
分塊請求
支援 .netrc
Requests 支援 Python 2.6—2.7以及3.3—3.7,而且能在 PyPy 下完美執行。
看到就會覺得很厲害了,不明覺厲啊~
萬事開頭難,先安裝走一波把 pip install requests,安裝完就可以用了~
2.教程
直接上例子:
傳送請求
import requests
r = requests.get(url='http://www.baidu.com') # 最基本的GET請求
r = requests.get(url='http://xxct.baidu.com/s', params={'wd':'python'}) #帶引數的GET請求
r = requests.post(url='http://xx', data={'wd':'python'}) #帶body的POST請求
r = requests.post('https://x', data=json.dumps({'wd': 'python'})) #帶JSON的POST
#其他請求方式的用法
requests.get(‘https://XX’) #GET請求
requests.post(“http://XX”) #POST請求
requests.put(“http://XX”) #PUT請求
requests.delete(“http://XX”) #DELETE請求
requests.head(“http://XX”) #HEAD請求
requests.options(“http://XX”) #OPTIONS請求
複製程式碼
響應內容
請求返回的值是一個Response 物件,Response 物件是對 HTTP 協議中服務端返回給瀏覽器的響應資料的封裝,響應的中的主要元素包括:狀態碼、原因短語、響應首部、響應體等等,這些屬性都封裝在Response 物件中。
# 狀態碼
response.status_code
200
# 原因短語
response.reason
'OK'
# 響應首部
for name,value in response.headers.items():
print("%s:%s" % (name, value))
Content-Encoding:gzip
Server:nginx/1.10.2
Date:Thu, 06 Apr 2017 16:28:01 GMT
# 響應內容
response.content
複製程式碼
查詢引數
很多URL都帶有很長一串引數,我們稱這些引數為URL的查詢引數,用"?"附加在URL連結後面,多個引數之間用"&"隔開,比如:http://www.baidu.com/?wd=python&key=20 ,現在你可以用字典來構建查詢引數:
args = {"wd": python, "time": 20}
response = requests.get("http://www.baidu.com", params = args)
response.url
'http://www.baidu.com/?wd=python&time=20'
複製程式碼
請求首部
requests 可以很簡單地指定請求首部欄位 Headers,比如有時要指定 User-Agent 偽裝成瀏覽器傳送請求,以此來矇騙伺服器。直接傳遞一個字典物件給引數 headers 即可。
r = requests.get(url, headers={'user-agent': 'Mozilla/5.0'})
複製程式碼
請求體
requests 可以非常靈活地構建 POST 請求需要的資料,
如果伺服器要求傳送的資料是表單資料,則可以指定關鍵字引數 data,
如果要求傳遞 json 格式字串引數,則可以使用json關鍵字引數,引數的值都可以字典的形式傳過去。
作為表單資料傳輸給伺服器
payload = {'key1': 'value1', 'key2': 'value2'}
r = requests.post("http://www.baidu.com", data=payload)
複製程式碼
作為 json 格式的字串格式傳輸給伺服器
import json
url = 'http://www.baidu.com'
payload = {'some': 'data'}
r = requests.post(url, json=payload)
複製程式碼
響應內容
響應體在 requests 中處理非常靈活,
與響應體相關的屬性有:content、text、json()。
content 是 byte 型別,適合直接將內容儲存到檔案系統或者傳輸到網路中
r = requests.get("https://pic1.zhimg.com/v2-2e92ebadb4a967829dcd7d05908ccab0_b.jpg")
type(r.content)
<class 'bytes'>
# 另存為 test.jpg
with open("test.jpg", "wb") as f:
f.write(r.content)
複製程式碼
text 是 str 型別,比如一個普通的 HTML 頁面,需要對文字進一步分析時,使用 text。
r = requests.get("https://www.baidu.com")
type(r.text)
<class 'str'>
re.compile('xxx').findall(r.text)
複製程式碼
如果使用第三方開放平臺或者API介面爬取資料時,返回的內容是json格式的資料時,那麼可以直接使用json()方法返回一個經過json.loads()處理後的物件。
>>> r = requests.get('https://www.baidu.com')
>>> r.json()
複製程式碼
代理設定
當爬蟲頻繁地對伺服器進行抓取內容時,很容易被伺服器遮蔽掉,因此要想繼續順利的進行爬取資料,使用代理是明智的選擇。如果你想爬取牆外的資料,同樣設定代理可以解決問題,requests 完美支援代理。
import requests
proxies = {
'http': 'http://XX',
'https': 'https://XX',
}
requests.get('https://foofish.net', proxies=proxies, timeout=5)
複製程式碼
超時設定
requests 傳送請求時,預設請求下執行緒一直阻塞,直到有響應返回才處理後面的邏輯。
如果遇到伺服器沒有響應的情況時,問題就變得很嚴重了,它將導致整個應用程式一直處於阻塞狀態而沒法處理其他請求。
r = requests.get("http://www.google.coma", timeout=5)
3.實戰1 爬取青春妹子網站
這次爬取的網站是:http://www.mmjpg.com/,直接F12看了下,所有的資訊都集中在ul區塊下的img區塊裡:
分析下網頁的結構,基本是,這樣的:
1)每一頁會有15個圖集
2)每一個圖集裡有N張圖片
而我們需要的就是下載圖片,那就是需要先獲取網頁分頁->圖集分頁->圖片下載頁,爬取的思路:
1)獲取當前網址的圖集地址
2)獲取當前網站的下載地址
3)下載圖片
先以首頁為例子,獲取首頁的所有圖集地址:
import requests
from lxml import html
def Get_Page_Number():
url = 'http://www.mmjpg.com'
response = requests.get(url).content
#呼叫requests庫,獲取二進位制的相應內容。
#注意,這裡使用.text方法的話,下面的html解析會報錯.這裡涉及到.content和.text的區別了。簡單說,如果是處理文字、連結等內容,建議使用.text,處理視訊、音訊、圖片等二進位制內容,建議使用.content。
selector = html.fromstring(response)
#使用lxml.html模組構建選擇器,主要功能是將二進位制的伺服器相應內容response轉化為可讀取的元素樹(element tree)。lxml中就有etree模組,是構建元素樹用的。如果是將html字串轉化為可讀取的元素樹,就建議使用lxml.html.fromstring,畢竟這幾個名字應該能大致說明功能了吧。
urls = []
#準備容器
for i in selector.xpath("//ul/li/a/@href"):
#利用xpath定位到所有的套圖的詳細地址
urls.append(i)
#遍歷所有地址,新增到容器中
return urls
複製程式碼
這裡的urls,就是圖集的地址,接下來就是獲取套圖地址的標題
這裡有同學有疑問,為什麼用xpatch,而不是用beautifulsoup?
單一例子來首,兩者都是用來解析網頁的,差別不大,主要是想熟悉瞭解下,關於xpath的內容,scrapy下面有詳細介紹~
def Get_Image_Title():
# 現在進入到套圖的詳情頁面了,現在要把套圖的標題和圖片總數提取出來
url = "http://www.mmjpg.com/mm/1367"
response = requests.get(url).content
selector = html.fromstring(response)
image_title = selector.xpath("//div[@class='article']/h2/text()")[0]
# 需要注意的是,xpath返回的結果都是序列,所以需要使用[0]進行定位
return image_title
複製程式碼
接下來獲取圖片的數量:
同樣的套圖頁,直接點位到座標處,會發現a標籤的倒數第二個區塊就是圖集的最後一頁,直接取數就行,程式碼如下:def Get_Image_Count():
url = "http://www.mmjpg.com/mm/1367"
response = requests.get(url).content
selector = html.fromstring(response)
image_count = selector.xpath("//div[@class='page']/a[last()-1]/text()")[0]
return image_count
複製程式碼
接下來就是獲取圖片的下載地址:
並且點選不同頁碼的圖片,變化的是最後一位: http://www.mmjpg.com/mm/1365/XX因此想獲取圖集下的所有圖片連結,就是先獲取有多少頁(上面的方法就可以啦),然後找到img獲取下載連結,程式碼如下:
def Get_Image_Url():
url = "http://www.mmjpg.com/mm/1367"
response = requests.get(url).content
selector = html.fromstring(response)
image_links = []
image_aount = selector.xpath("//div[@class='page']/a[last()-1]/text()")[0]
for i in range(int(image_aount)):
image_url = "http://www.mmjpg.com/mm/1367/"+str(i+1)
response = requests.get(image_url).content
sel = html.fromstring(response)
image_download_link = sel.xpath("//div[@class='content']/a/img/@src")[0]
# 這裡是單張圖片的最終下載地址
image_links.append(str(image_download_link))
return image_links
複製程式碼
最後,就是下載檔案啦~
def Download_Image(image_title,image_links):
num = 1
amount = len(image_links)
for i in image_links:
filename = "%s%s.jpg" % (image_title,num)
print('正在下載圖片:%s第%s/%s張,' % (image_title, num, amount))
#用於在cmd視窗上輸出提示,感覺可以增加一個容錯函式,沒想好怎麼寫
urllib.request.urlretrieve(requests.get(i,headers=header), "%s%s%s.jpg" % (dir, image_title, num))
num += 1
複製程式碼
跑起來後發現,爬下來的圖都是這樣的,但是把url資訊輸出看了下,連結都沒問題啊,奇怪了~
然後用PC玩了下,連結跟爬取的都沒問題,這種情況只有網站做了反爬蟲了;
既然如此,那我們修改下策略,上面在下載檔案的時候,用的是urlretrieve,但是現在需要在下載圖片時請求下,而且urlretrieve沒有可以設定請求的引數,因此不適用本場景;
urlretrieve(url, filename=None, reporthook=None, data=None)
引數 finename 指定了儲存本地路徑(如果引數未指定,urllib會生成一個臨時檔案儲存資料。)
引數 reporthook 是一個回撥函式,當連線上伺服器、以及相應的資料塊傳輸完畢時會觸發該回撥,我們可以利用這個回撥函式來顯示當前的下載進度。
引數 data 指 post 到伺服器的資料,該方法返回一個包含兩個元素的(filename, headers)元組,filename 表示儲存到本地的路徑,header 表示伺服器的響應頭。
複製程式碼
那我們就改成用wirte的方式去寫入圖片:
with open(dir+filename, 'wb') as f:
#以二進位制寫入的模式在本地構建新檔案
header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.32 Safari/537.36'}
f.write(requests.get(i,headers=header).content)
複製程式碼
執行後發現,依然是下載同一張圖片,那說明靠UA還不夠,再看下請求頭資訊:
也沒有什麼特別的,既然如此,就把整個頭部模擬一模一樣,最後發現,還需要Referer這個引數:對應的值,貌似就是上面的i,因此修改成這樣: for i in image_links:
filename = "%s%s.jpg" % (image_title, num)
print('正在下載圖片:%s第%s/%s張,' % (image_title, num, amount))
# 用於在cmd視窗上輸出提示,感覺可以增加一個容錯函式,沒想好怎麼寫
with open(dir+filename, 'wb') as f:
#以二進位制寫入的模式在本地構建新檔案
header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.32 Safari/537.36',
'Referer':i}
f.write(requests.get(i,headers=header).content)
複製程式碼
執行後發現,每張圖片大小都不一樣,嗯,終於成功啦~
整體程式碼如下:
import requests
from lxml import html
import os
import urllib
dir = "mmjpg/"
def Get_Page_Number(num):
if (int(num) < 2):
url = 'http://www.mmjpg.com'
else:
url = 'http://www.mmjpg.com/home/' + num
response = requests.get(url).content
# 呼叫requests庫,獲取二進位制的相應內容。
# 注意,這裡使用.text方法的話,下面的html解析會報錯.這裡涉及到.content和.text的區別了。簡單說,如果是處理文字、連結等內容,建議使用.text,處理視訊、音訊、圖片等二進位制內容,建議使用.content。
selector = html.fromstring(response)
# 使用lxml.html模組構建選擇器,主要功能是將二進位制的伺服器相應內容response轉化為可讀取的元素樹(element tree)。lxml中就有etree模組,是構建元素樹用的。如果是將html字串轉化為可讀取的元素樹,就建議使用lxml.html.fromstring,畢竟這幾個名字應該能大致說明功能了吧。
urls = []
# 準備容器
for i in selector.xpath("//ul/li/a/@href"):
# 利用xpath定位到所有的套圖的詳細地址
urls.append(i)
# 遍歷所有地址,新增到容器中
return urls
def Get_Image_Title(url):
# 現在進入到套圖的詳情頁面了,現在要把套圖的標題和圖片總數提取出來
response = requests.get(url).content
selector = html.fromstring(response)
image_title = selector.xpath("//div[@class='article']/h2/text()")[0]
# 需要注意的是,xpath返回的結果都是序列,所以需要使用[0]進行定位
return image_title
def Get_Image_Count(url):
response = requests.get(url).content
selector = html.fromstring(response)
image_count = selector.xpath("//div[@class='page']/a[last()-1]/text()")[0]
return image_count
def Get_Image_Url(url):
response = requests.get(url).content
selector = html.fromstring(response)
image_links = []
image_aount = selector.xpath("//div[@class='page']/a[last()-1]/text()")[0]
for i in range(int(image_aount)):
image_url = url + "/" + str(i + 1)
response = requests.get(image_url).content
sel = html.fromstring(response)
image_download_link = sel.xpath("//div[@class='content']/a/img/@src")[0]
# 這裡是單張圖片的最終下載地址
image_links.append(str(image_download_link))
return image_links
def Download_Image(image_title, image_links):
num = 1
amount = len(image_links)
if not os.path.exists(dir):
os.makedirs(dir)
for i in image_links:
if not os.path.exists(dir+image_title):
os.makedirs(dir+image_title)
print('正在下載圖片:%s第%s/%s張,' % (image_title, num, amount))
# 用於在cmd視窗上輸出提示,感覺可以增加一個容錯函式,沒想好怎麼寫
filename = image_title+"/"+str(num)+".jpg"
#建立檔名
with open(dir+filename, 'wb') as f:
#以二進位制寫入的模式在本地構建新檔案
header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.32 Safari/537.36',
'Referer':i}
f.write(requests.get(i,headers=header).content)
# urllib.request.urlretrieve(requests.get(i,headers=header), "%s%s%s.jpg" % (dir, image_title, num))
#如果使用這種方式爬,網站會返回防盜連結,爬的圖片都一樣,因此需要爬的時候UA做下處理,而urlretrieve並沒有設定請求頭的方式,因此不適用本案例
num += 1
if __name__ == '__main__':
page_number = input('請輸入需要爬取的頁碼:')
for link in Get_Page_Number(page_number):
Download_Image(Get_Image_Title(link), Get_Image_Url(link))
複製程式碼
整體程式碼與演示程式碼有點出入,主要是整理了一下,主體思路不變~效果如下:
該實戰完畢,主要還是能整理出思路,問題則不大~
Scrapy
自己單獨寫爬蟲的話,會用很多重複的程式碼,比如設定headers,代理IP,檔案儲存,雖然每次手動寫,都覺得好爽的,但今天來介紹一個出名的爬蟲框架--Scrapy
1.為什麼要用爬蟲框架?
如果你對爬蟲的基礎知識有了一定了解的話,那麼是時候該瞭解一下爬蟲框架了。那麼為什麼要使用爬蟲框架?
1)學習框架的根本是學習一種程式設計思想,而不應該僅僅侷限於是如何使用它。從瞭解到掌握一種框架,其實是對一種思想理解的過程。
2)框架也給我們的開發帶來了極大的方便。許多條條框框都已經是寫好了的,並不需要我們重複造輪子,我們只需要根據自己的需求定製自己要實現的功能就好了,大大減少了工作量。
。
2.簡介
官網文件:http://scrapy-chs.readthedocs.io/zh_CN/0.24/intro/overview.html
Scrapy是一個為了爬取網站資料,提取結構性資料而編寫的應用框架。
可以應用在包括資料探勘,資訊處理或儲存歷史資料等一系列的程式中。
其最初是為了 頁面抓取 (更確切來說, 網路抓取 )所設計的,
也可以應用在獲取API所返回的資料(例如 Amazon Associates Web Services ) 或者通用的網路爬蟲。
3.scrapy特點:
1)scrapy基於事件的機制,利用twisted的設計實現了非阻塞的非同步操作。
這相比於傳統的阻塞式請求,極大的提高了CPU的使用率,以及爬取效率。
2)配置簡單,可以簡單的通過設定一行程式碼實現複雜功能。
3)可擴充,外掛豐富,比如分散式scrapy + redis、爬蟲視覺化等外掛。
4)解析方便易用,scrapy封裝了xpath等解析器,提供了更方便更高階的selector構造器,可有效的處理破損的HTML程式碼和編碼。
4.scrapy安裝
pip install scrapy
複製程式碼
在Windows上安裝時可能會出現錯誤,提示找不到Microsoft Visual C++。這時候我們需要到它提示的網站visual-cpp-build-tools下載VC++ 14編譯器,安裝完成之後再次執行命令即可成功安裝Scrapy。
官網連結:http://landinghub.visualstudio.com/visual-cpp-build-tools
但實際電腦還是會一直報找不到Microsoft Visual C++,後來網上查詢後,使用Anaconda安裝就好,安裝包如下:
連結:https://pan.baidu.com/s/1a-VxwaR56iQQu108wqz2zA,密碼:r1oz
下載完後直接安裝,安裝完成後直接CMD命令列輸入conda install scrapy,安裝完成後是這樣的:
驗證的話,直接輸入scrapy -h,能顯示內容即可~
在Ubuntu安裝的時候,中途出現一個錯誤:fatal error: 'Python.h' file not found 需要另外安裝python-dev,該庫中包含Python的標頭檔案與靜態庫包, 要根據自己的Python版本進行安裝:
sudo apt-get install python3.4-dev
複製程式碼
5. Scrapy框架路過了解下
架構圖
模組介紹:
1)Engine。引擎,處理整個系統的資料流處理、觸發事務,是整個框架的核心。
2)Item。專案,它定義了爬取結果的資料結構,爬取的資料會被賦值成該Item物件。
3)Scheduler。排程器,接受引擎發過來的請求並將其加入佇列中,在引擎再次請求的時候將請求提供給引擎。
4)Downloader。下載器,下載網頁內容,並將網頁內容返回給蜘蛛。Spiders。蜘蛛,其內定義了爬取的邏輯和網頁的解析規則,它主要負責解析響應並生成提取結果和新的請求。
5)Item Pipeline。專案管道,負責處理由蜘蛛從網頁中抽取的專案,它的主要任務是清洗、驗證和儲存資料。
6)Downloader Middlewares。下載器中介軟體,位於引擎和下載器之間的鉤子框架,主要處理引擎與下載器之間的請求及響應。
7)Spider Middlewares。蜘蛛中介軟體,位於引擎和蜘蛛之間的鉤子框架,主要處理蜘蛛輸入的響應和輸出的結果及新的請求。
執行過程
1)引擎首先開啟一個網站,找到處理該網站的Spider,並向該Spider請求第一個要爬取的URL。
2)引擎從Spider中獲取到第一個要爬取的URL,並通過Scheduler以Request的形式排程。
3)引擎向Scheduler請求下一個要爬取的URL。
4)Scheduler返回下一個要爬取的URL給引擎,引擎將URL通下載器中介軟體轉發給Downloader下載。
5)一旦頁面下載完畢,Downloader生成該頁面的Response,並將其通過下載器中介軟體傳送給引擎。
6)引擎從下載器中接收到Response,並將其通過Spider Middlewares傳送給Spider處理。
7)Spider處理Response,並返回爬取到的Item及新的Request給引擎。
8)引擎將Spider返回的Item給Item Pipeline,將新的Request給Scheduler。
9)重複第二步到最後一步,直到Scheduler中沒有更多的Request,引擎關閉該網站,爬取結束。
6.專案結構
新建的專案結構如下:
ScrapyStudy/
scrapy.cfg # 專案的配置檔案
ScrapyStudy/ # 該專案的python模組,程式碼都加在裡面
__init__.py
items.py # 專案中的item檔案
pipelines.py # 專案中pipelines檔案
settings.py # 專案的設定檔案
spiders/ # 方式spider程式碼的目錄
__init__.py
複製程式碼
檔案描述如下:
1)scrapy.cfg:它是Scrapy專案的配置檔案,其內定義了專案的配置檔案路徑、部署相關資訊等內容。
2)items.py:它定義Item資料結構,所有的Item的定義都可以放這裡。
3)pipelines.py:它定義Item Pipeline的實現,所有的Item Pipeline的實現都可以放這裡。
4)settings.py:它定義專案的全域性配置。
5)middlewares.py:它定義Spider Middlewares和Downloader Middlewares的實現。
6)spiders:其內包含一個個Spider的實現,每個Spider都有一個檔案
7.製作爬蟲步驟
1)新建專案(scrapy startproject xxx): 新建一個新的爬蟲專案
2)明確目標(編寫items.py): 明確你想要抓取的目標
3)製作爬蟲(spiders/xxsp der.py): 製作爬蟲開始爬取網頁
4)儲存內容(pipelines.py): 設計管道儲存爬取內容
8.實戰1 使用scrapy輸出廖雪峰官網的title
根據7得知,製作爬蟲需要4個步驟,那現在就以實戰來介紹這4個步驟;
1)建立專案:
scrapy startproject 專案名
複製程式碼
這樣就代表新建完成了
然後用pycharm開啟了建立的專案後,就開始體驗啦~
以廖雪峰python官網為例子,輸出title資訊;
再spiders目錄下新增一個檔案,比如liaoxuefeng.py,程式碼如下:
import scrapy
class LiaoxuefengSpider(scrapy.Spider):
# 這裡是將爬蟲定義為scrapy.Spider這個類下的一個例項。
# Spider這個類定義了爬蟲的很多基本功能,我們直接例項化就好,
# 省卻了很多重寫方法的麻煩。
name = 'lxf'
#這是爬蟲的名字,這個非常重要。
start_urls = ['http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000']
#這是爬蟲開始幹活的地址,必須是一個可迭代物件。
def parse(self, response):
#爬蟲收到上面的地址後,就會傳送requests請求,在收到伺服器返回的內容後,就將內容傳遞給parse函式。在這裡我們重寫函式,達到我們想要的功能。
titles = response.xpath("//ul[@class='uk-nav uk-nav-side']//a/text()").extract()
#這是廖雪峰老師python教程的標題列表。我們利用xpath解析器對收到的response進行分析,從而提取出我們需要的資料。//XXX表示任何任何目錄下的XXX區塊,/XXX表示子目錄下的XXX區塊,XXX[@class=abc]表示帶有class=abc屬性值的XXX區塊,/text()表示獲取該區塊的文字。最後加上.extract()表示將內容提取出來。
for title in titles:
print (title)
#這個沒什麼說的了,直接遍歷,然後列印標題。
複製程式碼
然後進入cmd,在專案的根目錄下執行scrapy crawl lxf(這個lxf就是剛才liaoxuefeng.py檔案中的name欄位,千萬不要弄錯了),執行成功,當觀察發現,並沒有所需要的內容,直接提示“503 Service Unavailable”,
根據經驗,這是因為沒有設定請求頭導致的;
那在setting.py,找到USER_AGENT這個引數,預設是註釋的,取消註釋後,提供value,比如:
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.4843.400 QQBrowser/9.7.13021.400
spider必須定義三個屬性:
-name: 用於區別Spider。 該名字必須是唯一的,您不可以為不同的Spider設定相同的名字。
-start_urls: 包含了Spider在啟動時進行爬取的url列表。 因此,第一個被獲取到的頁面將是其中之一。 後續的URL則從初始的URL獲取到的資料中提取。
-parse() 是spider的一個方法。 被呼叫時,每個初始URL完成下載後生成的 Response 物件將會作為唯一的引數傳遞給該函式。 該方法負責解析返回的資料,提取資料(生成item)以及生成需要進一步處理的URL的 Request 物件。
然後再次執行scrapy crawl lxf,就會列印當前頁面所有的目錄名稱:
上面提及到一個知識點:取出網頁中想要的資訊
Scrapy中使用一種基於XPath和CSSDE表示式機制:Scrapy Selectors
來提取出網頁中我們所需的資料。
Selector是一個選擇,有四個基本方法:
1)xpath():傳入xpath表示式,返回該表示式對應的所有節點的selector list列表;
2)css():傳入CSS表示式,返回該表示式對應的所有及誒點的selector list列表;
3)extract():序列化該節點為unicode字串並返回list;
4)re():根據傳入的正規表示式對資料進行提取,返回unicode字串list列表;
這裡順道學下XPath的基本語法:
首先XPath中的路徑分為絕對路徑與相對路徑:
絕對路徑:用**/,表示從根節點開始選取;
相對路徑:用//,表示選擇任意位置的節點,而不考慮他們的位置;
另外可以使用*萬用字元來表示未知的元素;除此之外還有兩個選取節點的:
.:選取當前節點;..:當前節點的父節點;
接著就是選擇分支進行定位了,比如存在多個元素,想唯一定位,
可以使用[]**中括號來選擇分支,下標是從1開始算的哦!
比如可以有下面這些玩法:
1)/tr/td[1]:取第一個td
2)/tr/td[last()]:取最後一個td
3)/tr/td[last()-1]:取倒數第二個td
4)/tr/td[position()<3]:取第一個和第二個td
5)/tr/td[@class]:選取擁有class屬性的td
6)/tr/td[@class='xxx']:選取擁有class屬性為xxx的td
7)/tr/td[count>10]:選取 price 元素的值大於10的td
然後是選擇屬性,其實就是上面的這個**@**
可以使用多個屬性定位,可以這樣寫:/tr/td[@class='xxx'][@value='yyy']
或者**/tr/td[@class='xxx' and @value='yyy']**
接著是常用函式:除了上面的last(),position(),外還有: contains(string1,string2):如果前後匹配返回True,不匹配返回False; text():獲取元素的文字內容 start-with():從起始位置匹配字串
回顧
第一個實戰專案就到此結束啦~
是不是很簡單?
遇到一個問題:
在用pycharm開啟scrapy專案後,scrapy一直在顯示紅色報錯,當時介意了很久,心想著,本地用都能用,為什麼你這邊報錯?
結果後來發現,原來scrapy不是在pycharm上執行的,報錯也不需要管~本地確認scrapy有安裝就行了~
9.實戰2 使用scrapy輸出廖雪峰官網的title
1)建立專案,直接在需要的目錄下執行scrapy startproject 專案名
2)在spiders目錄下新建爬蟲檔案,比如本例叫LiaoxuefengSpider,建立後,需要思考在裡面填寫什麼?
填寫需要爬取的網站;
設定爬蟲名稱,這裡注意,該名稱是全域性唯一的,不允許重複;
介面解析;
內容輸出;
因此不難寫出下面的程式碼;
import scrapy
class LiaoxuefengSpider(scrapy.Spider):
# 這裡是將爬蟲定義為scrapy.Spider這個類下的一個例項。
# Spider這個類定義了爬蟲的很多基本功能,我們直接例項化就好,
# 省卻了很多重寫方法的麻煩。
name = 'lxf'
#這是爬蟲的名字,這個非常重要。
start_urls = ['https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000']
#這是爬蟲開始幹活的地址,必須是一個可迭代物件。
def parse(self, response):
#爬蟲收到上面的地址後,就會傳送requests請求,在收到伺服器返回的內容後,就將內容傳遞給parse函式。在這裡我們重寫函式,達到我們想要的功能。
titles = response.xpath("//ul[@class='uk-nav uk-nav-side']//a/text()").extract()
#這是廖雪峰老師python教程的標題列表。我們利用xpath解析器對收到的response進行分析,從而提取出我們需要的資料。//XXX表示任何任何目錄下的XXX區塊,/XXX表示子目錄下的XXX區塊,XXX[@class=abc]表示帶有class=abc屬性值的XXX區塊,/text()表示獲取該區塊的文字。最後加上.extract()表示將內容提取出來。
for title in titles:
print (title)
#這個沒什麼說的了,直接遍歷,然後列印標題。
複製程式碼
編寫後,在專案目錄下執行scrapy crawl 爬蟲名稱,如scrapy crawl lxf,就會執行,但是會發現報錯:
從截圖資訊,看到伺服器返回503,根據經驗,這是因為沒有設定請求headers導致,因此有兩種方案:1)在爬蟲檔案裡設定請求headers
2)開啟專案裡的settings.py,找到USER_AGENT,預設是被註釋的,關閉註釋,並且給一個預設的UA即可,如:
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
再次輸入scrapy crawl爬蟲名稱,發現能正常顯示了,如下:
這就說明指令碼能正常執行,同時也說明scrapy第一個例子成功啦~
重要的資訊重複強調,爬蟲名稱是全域性唯一的~
10.實戰3 使用scrapy爬取電影圖片
先建立專案:scrapy startproject 專案名
建立完專案之後,在spiders建立一個檔案,建立後,專案結構如下:
想爬的url連結是這個:http://www.id97.com/movie/,先看看我們想爬什麼~
從上圖看出,可以爬的東西有,電影名稱,型別,分數以及封面連結
既然都已經決定了,那還記得,哪個檔案是用來存放目標的嗎?沒錯,就是item.py
import scrapy
class MovieScrapyItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
articleUrl = scrapy.Field()
movieName = scrapy.Field()
scoreNumber = scrapy.Field()
style = scrapy.Field()
複製程式碼
不難得出上面的內容,那我們繼續分析網頁結構~
直接點選,發現每一部電影的資料都是在獨立的class裡面,這個class叫col-xs-1-5 col-sm-4 col-xs-6 movie-item這也意味著,上面需要的內容,都可以獨立獲取了,那div這塊可以這樣寫了
def parse(self,response):
selector = Selector(response)
divs = selector.xpath("//div[@class='col-xs-1-5 col-sm-4 col-xs-6 movie-item']")
複製程式碼
回顧上面內容,什麼是Selector?
Scrapy提取資料有自己的一套機制。它們被稱作選擇器(seletors),因為他們通過特定的 XPath 或者 CSS 表示式來“選擇” HTML檔案中的某個部分;
剛剛上面提及到了,所有的電影內容都在不同的class裡面,但都叫col-xs-1-5 col-sm-4 col-xs-6 movie-item,因此只需要直接找這個class, 那返回的就是所有div資訊;
那隻需要寫個for,即可獲取每個div的資訊
for div in divs:
yield self.parse_item(div)
複製程式碼
知識點,yield是啥?
yield的作用是把parse函式變成一個發生器(generator),每次函式執行會返回一個迭代物件(iterable 物件)。聽上去是不是跟return一樣好像一樣的感覺?
return返回的是函式返回值,而yield返回的是一個生成器~
關於如何更好理解yield,這邊貼一個外鏈,感興趣的同學可以瞭解:
http://pyzh.readthedocs.io/en/latest/the-python-yield-keyword-explained.html
扯遠了,上面的程式碼,parse_item這個方法不是自帶的,是自己寫的,用來處理的就是針對每個電影div做詳細的資料獲取,那繼續看分析吧~
隨便挑一個開啟,就能看到我們需要的內容了,標題,封面url,分數,型別,其中,標題跟url可以直接獲取到,而分數跟型別還要做下處理~
首先,第一步,獲取資料,如下:
item['articleUrl'] = div.xpath('div[@class="movie-item-in"]/a/img/@data-original ').extract_first()
item['movieName'] = div.xpath('div[@class="movie-item-in"]/a/@title').extract_first()
item['scoreNumber'] = div.xpath('div[@class="movie-item-in"]/div[@class="meta"]/h1/em/text()').extract_first()
item['style']= div.xpath(
'string(div[@class="movie-item-in"]/div[@class="meta"]/div[@class="otherinfo"])').extract_first(
default="")
複製程式碼
上面這樣獲取是不是就行了?不是的,分數那是不對的,因為通過xpath獲取到的分數是這樣的: - 6.8分,
而我們需要的是6.8,那以意味著分數需要二次處理,也很簡單,直接正則提取就行了,不詳細說明,原始碼如下:
def parse_item(self,div):
item = MovieScrapyItem()
#封面url
item['articleUrl'] = div.xpath('div[@class="movie-item-in"]/a/img/@data-original ').extract_first()
#電影名稱
item['movieName'] = div.xpath('div[@class="movie-item-in"]/a/@title').extract_first()
#分數,但需要處理
score = div.xpath('div[@class="movie-item-in"]/div[@class="meta"]/h1/em/text()').extract_first()
item['scoreNumber'] = self.convertScore(score)
#型別
style = div.xpath(
'string(div[@class="movie-item-in"]/div[@class="meta"]/div[@class="otherinfo"])').extract_first(
default="")
item['style'] = style
#用來提取分數的
def convertScore(self, str):
list = re.findall(r"\d+\.?\d*", str)
if list:
return list.pop(0)
else:
return 0
複製程式碼
有同學會有一個疑問,extract_first()跟extract()區別在哪裡?
extract()返回的是資料,extract_first()返回的是字串,不信?看看下面~
結果發現,str(extract)輸出的就是一個資料,如果用於拼接,還需要額外處理的" ".join(str(extract))這樣處理,那還不如直接一個是extract_first()返回字串來的快~
那如果我們想翻頁繼續爬呢?當然沒問題啦~
直接點選下一頁,它的結構如下,是在一個叫pager-gb的class裡面,而下一頁的按鈕就在倒數第二個li裡面~
#定位到頁數這樣
pages = selector.xpath("body/div/div[@class='pager-bg']/ul/li")
#下一頁
nextPageUrl = self.host + (pages[- 2].xpath('a/@href').extract_first())
複製程式碼
這裡的self.host是檔案開頭定義好的,是"http://www.id97.com",用於拼接的;
這樣就能獲取到下一頁的連結了,那接下來就是再次發起請求即可;
import 下Request,直接執行即可;
yield Request(nextPageUrl,callback=self.parse)
複製程式碼
完整程式碼文末貼出,彆著急=。=
至此,爬蟲部分搞定了,item裡面的內容就是我們要的東西了~
那,能否把這些資訊儲存到csv裡面?
當然可以,沒問題,而且還不需要改動程式碼哦~
直接在執行命令的時候,加多幾個引數即可~
scrapy crawl 專案名 -o xx.csv
複製程式碼
這樣就能生成csv檔案~但是開啟後,辣眼睛啊~都亂碼?
網上找了下,原因是:
微軟的軟體開啟檔案預設都是 ANSI 編碼(國內就是 GBK),UTF-8 的 csv 檔案在 execl 中開啟時解碼自然就亂碼了~
解決方案呢?技術層面貌似找了好久都沒找到,就是encode啥指定編碼都不行,因此只能這樣: 用記事本開啟剛剛儲存的csv,點選另存為,不再使用utf-8,然後再開啟就好了~
既然都有url了,那能存拿url出來進行儲存?
沒問題,都可以滿足~
儲存圖片有2種方式:
1)urlib.request.urlretrieve
2)scrapy內建的ImagePipeline
第一種不需要解釋了,上篇文章已經有說明,使用場景就是在獲取url的時候,直接下載,效率會慢點:
if item['articleUrl']:
file_name = "%s.jpg"%(item['movieName'])
file_path = os.path.join("F:\\pics", file_name)
urllib.request.urlretrieve(item['articleUrl'], file_path)
複製程式碼
第二種,需要修改pipelines.py這個檔案,直接重寫即可,規則自己定義:
class MovieScrapyPipeline(ImagesPipeline):
# def process_item(self, item, spider):
# return item
# 在工作流程中可以看到,管道會得到圖片的URL並從專案中下載。
# # 為了這麼做,你需要重寫 get_media_requests() 方法,並對各個圖片URL返回一個Request:
def get_media_requests(self, item, info):
# 這裡把item傳過去,因為後面需要用item裡面的name作為檔名
yield Request(item['articleUrl'])
#修改圖片生存名稱規則
def file_path(self, request, response=None, info=None):
item = request.meta['item']
image_guid = request.url.split('/')[-1] # 倒數第一個元素
filenames = "full/%s/%s" % (item['name'], image_guid)
# print(filename)
return filenames
複製程式碼
修改完pipelinse後,還要修改下settings.py檔案;
找到ITEM_PIPELINES把註釋去掉,啟用pinelines, 把我們自定義的PicPipeLine加上,還有順道設定下下載圖片的存放位置:
ITEM_PIPELINES = {
'movie_scrapy.pipelines.MovieScrapyPipeline':300,
}
IMAGES_STORE = "F:\pics"
複製程式碼
然後再執行,圖片的嗶哩吧啦的下載啦~
這裡遇到一個問題,自帶的ImagePipeline儲存圖片功能,同一份程式碼,在Linux下可以下載圖片,但是在Windows下就不能下載,
但是程式碼沒報錯,沒找到原因,所以後來才用urlretrieve下載的,不知道有同學知道原因嗎?
原始碼如下: movie_spiders.py
import scrapy
from scrapy.selector import Selector
from scrapy import Request
from movie_scrapy.items import MovieScrapyItem
import re
import os
import urllib
class movie_spiders(scrapy.Spider):
name = "movie"
host = "http://www.id97.com"
start_urls = ["http://www.id97.com/movie/"]
def parse(self,response):
selector = Selector(response)
divs = selector.xpath("//div[@class='col-xs-1-5 col-sm-4 col-xs-6 movie-item']")
pages = selector.xpath("body/div/div[@class='pager-bg']/ul/li")
for div in divs:
yield self.parse_item(div)
nextPageUrl = self.host + (pages[- 2].xpath('a/@href').extract_first())
yield Request(nextPageUrl,callback=self.parse)
def parse_item(self,div):
item = MovieScrapyItem()
item['articleUrl'] = div.xpath('div[@class="movie-item-in"]/a/img/@data-original ').extract_first()
item['movieName'] = div.xpath('div[@class="movie-item-in"]/a/@title').extract_first()
score = div.xpath('div[@class="movie-item-in"]/div[@class="meta"]/h1/em/text()').extract_first()
item['scoreNumber'] = self.convertScore(score)
style = div.xpath(
'string(div[@class="movie-item-in"]/div[@class="meta"]/div[@class="otherinfo"])').extract_first(
default="")
item['style'] = style
#下載圖片使用
if item['articleUrl']:
file_name = "%s.jpg"%(item['movieName'])
file_path = os.path.join("F:\\pics", file_name)
urllib.request.urlretrieve(item['articleUrl'], file_path)
return item
def convertScore(self, str):
list = re.findall(r"\d+\.?\d*", str)
if list:
return list.pop(0)
else:
return 0
複製程式碼
items.py
import scrapy
class MovieScrapyItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
articleUrl = scrapy.Field()
movieName = scrapy.Field()
scoreNumber = scrapy.Field()
style = scrapy.Field()
複製程式碼
pipelines.py
# -*- coding: utf-8 -*-
from scrapy.pipelines.images import ImagesPipeline
from scrapy.http import Request
class MovieScrapyPipeline(ImagesPipeline):
# def process_item(self, item, spider):
# return item
# 在工作流程中可以看到,管道會得到圖片的URL並從專案中下載。
# # 為了這麼做,你需要重寫 get_media_requests() 方法,並對各個圖片URL返回一個Request:
def get_media_requests(self, item, info):
# 這裡把item傳過去,因為後面需要用item裡面的name作為檔名
yield Request(item['articleUrl'])
#修改圖片生存名稱規則
def file_path(self, request, response=None, info=None):
item = request.meta['item']
image_guid = request.url.split('/')[-1] # 倒數第一個元素
filenames = "full/%s/%s" % (item['name'], image_guid)
# print(filename)
return filenames
複製程式碼
settings.py # -- coding: utf-8 --
BOT_NAME = 'movie_scrapy'
SPIDER_MODULES = ['movie_scrapy.spiders']
NEWSPIDER_MODULE = 'movie_scrapy.spiders'
ROBOTSTXT_OBEY = True
ITEM_PIPELINES = {
'movie_scrapy.pipelines.MovieScrapyPipeline':300,
}
IMAGES_STORE = "F:\pics"
複製程式碼
小結
本章大方向學習了requests跟scrapy,但細節點還是不少:
xpath,yield,反爬蟲,網頁結構分析,為後續爬蟲做下預熱學習;
下文預告:
本來有些實戰例子是想選擇知乎等平臺的,還是發現登入的時候需要各種驗證碼,比如說選擇倒立的圖片等等,
所以下篇文章就以模擬登入,如何跨過驗證碼為前提去做,初步想法是覆蓋英文/數字驗證碼,滑動驗證碼,倒立點選驗證碼以及12306驗證碼~
碎片時間寫了10天,終於結束了,謝謝大家~