前序
原本說好隨緣更新的,由於想去寫測試相關的文檔,但想一想,既然第一篇爬蟲文章都發布吧,就繼續完善吧~
css
上一章回顧:JB的Python之旅-爬蟲篇--urllib和Beautiful Souphtml
看回以前寫的爬蟲計劃:
python
關於後續爬蟲的計劃:
目前還處於初級的定向腳本編寫,本文內容主要介紹requests庫跟Scrapy框架;
計劃下一篇是Selenium,再下一篇是分佈式爬蟲,後面加點實戰,再看看怎麼更新吧~nginx
上一篇主要介紹了urllib 跟 BeautifuSoap,練手的項目有小說網站及百度圖片的爬取,對於平常工做來講,感受是夠了~web
但只要有瞭解過爬蟲,確定聽過requests跟Scrapy,這兩個又是什麼?
簡單介紹,requests是一個第三方庫,正如其名,就是處理請求相關內容,比起Python自帶的urllib庫,用起來相對的方便;正則表達式
Scrapy呢,度娘給的介紹是:redis
Python開發的一個快速、高層次的屏幕抓取和web抓取框架,用於抓取web站點並從頁面中提取結構化的數據;
Scrapy吸引人的地方在於它是一個框架,任何人均可以根據需求方便的修改。它也提供了多種類型爬蟲的基類,如BaseSpider、sitemap爬蟲等;
複製代碼
那,request是跟scrapy區別在哪裏?
從使用層面來講,單純爬取一個網頁的話,二者區別不大,本質是同樣的,但scrapy是一個專業爬蟲框架,假如須要派去1W個網站的時候,差別就出現了,須要作併發,監控存儲,scrapy是能夠作,但request這塊貌似作不到;
編程
從代碼層面,scrapy裏面可使用requests的內容,會有較多二次封裝,在使用上更加方便~
json
更多的內容就度娘吧,這裏不展開了,留個大概印象就好了~
瀏覽器
另外,本文還會使用另一個網頁解析方法--xpath,上篇文章咱們學會了用beautifulsoup,那爲何要用xpath?
beautifulsoup是先把整個網頁結構解析,而後再查找相關內容,而xpath是直接查找,不須要作額外的解析操做,
所以能夠得出,在性能效率上,xpath是灰常灰常的快的~
本小節主要介紹下requests的庫常見使用,以及會介紹一個內容實戰~
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,安裝完就能夠用了~
直接上例子:
發送請求
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)
此次爬取的網站是: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))
複製代碼
總體代碼與演示代碼有點出入,主要是整理了一下,主體思路不變~效果以下:
該實戰完畢,主要仍是能整理出思路,問題則不大~
本身單獨寫爬蟲的話,會用不少重複的代碼,好比設置headers,代理IP,文件保存,雖然每次手動寫,都以爲好爽的,但今天來介紹一個出名的爬蟲框架--Scrapy
若是你對爬蟲的基礎知識有了必定了解的話,那麼是時候該瞭解一下爬蟲框架了。那麼爲何要使用爬蟲框架?
1)學習框架的根本是學習一種編程思想,而不該該僅僅侷限因而如何使用它。從瞭解到掌握一種框架,實際上是對一種思想理解的過程。
2)框架也給咱們的開發帶來了極大的方便。許多條條框框都已是寫好了的,並不須要咱們重複造輪子,咱們只須要根據本身的需求定製本身要實現的功能就行了,大大減小了工做量。 。
官網文檔:http://scrapy-chs.readthedocs.io/zh_CN/0.24/intro/overview.html
Scrapy是一個爲了爬取網站數據,提取結構性數據而編寫的應用框架。
能夠應用在包括數據挖掘,信息處理或存儲歷史數據等一系列的程序中。
其最初是爲了 頁面抓取 (更確切來講, 網絡抓取 )所設計的,
也能夠應用在獲取API所返回的數據(例如 Amazon Associates Web Services ) 或者通用的網絡爬蟲。
1)scrapy基於事件的機制,利用twisted的設計實現了非阻塞的異步操做。
這相比於傳統的阻塞式請求,極大的提升了CPU的使用率,以及爬取效率。 2)配置簡單,能夠簡單的經過設置一行代碼實現複雜功能。 3)可拓展,插件豐富,好比分佈式scrapy + redis、爬蟲可視化等插件。 4)解析方便易用,scrapy封裝了xpath等解析器,提供了更方便更高級的selector構造器,可有效的處理破損的HTML代碼和編碼。
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
複製代碼
架構圖
模塊介紹:
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,引擎關閉該網站,爬取結束。
新建的項目結構以下:
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都有一個文件
1)新建項目(scrapy startproject xxx): 新建一個新的爬蟲項目
2)明確目標(編寫items.py): 明確你想要抓取的目標
3)製做爬蟲(spiders/xxsp der.py): 製做爬蟲開始爬取網頁
4)存儲內容(pipelines.py): 設計管道存儲爬取內容
根據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有安裝就好了~
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致使,所以有兩種方案:再次輸入scrapy crawl爬蟲名稱,發現能正常顯示了,以下:
這就說明腳本能正常執行,同時也說明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-itemdef 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()返回的是字符串,不信?看看下面~
那若是咱們想翻頁繼續爬呢?固然沒問題啦~
直接點擊下一頁,它的結構以下,是在一個叫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天,終於結束了,謝謝你們~