說到爬蟲,咱們可能會想到利用互聯網快速地獲取咱們須要的網絡資源,例如圖片、文字或者視頻。這是真的,若是咱們掌握了爬蟲技術,咱們就能夠在短期爬取網絡上的東西。可是,咱們能爬取的網上資源都是對方網站上直接擺出的內容,意味着咱們能夠手動的複製粘貼來獲取,而爬蟲只是代替了咱們的人工,並加快這一過程而已。在進行編寫網絡爬蟲以前,咱們必需要確保咱們所想獲取的網絡資源有跡可循,知道咱們所要的網絡資源在哪一個網站或者請求接口,而後咱們還要確保咱們所須要的資源是可獲取的,由於有時候若是對方不但願本身的資源被隨意爬取,他們的網站可能存在一些驗證機制,來攔截網絡爬蟲。css
進行網絡爬蟲的具體過程就是:批量獲取網站網頁的html內容(或者經過網站的資源接口獲取數據)->編寫數據處理代碼處理數據轉化爲咱們所須要的資源。若是有網站的數據接口,爬蟲就會簡單不少,咱們通常使用requests就能夠直接獲取數據。然而大多數狀況是,咱們須要的數據(或數據接口)是在網頁的html中,在正式獲取數據以前咱們還須要先進行html的頁面內容解析及提取。html頁面獲取,內容解析,數據提取還有最後的數據存儲,有時還須要考慮防爬蟲機制,這看起來須要進行很多的工做,而python有個第三方庫,能夠幫助咱們簡化這些工做,而且提升爬蟲效率。它就是scrapy。html
如Scrapy官網所說,Scrapy是一個用於爬取網站數據,提取結構性數據的應用框架。今天咱們就用scrapy來開始嘗試咱們的第一次爬蟲。前端
咱們新建一個文件夾用於開展咱們的爬蟲項目,名爲douban_scrapy。此次咱們要爬取的目標資源是豆瓣的高分電影,豆瓣有人作高分電影彙總,可是有點很差的是咱們沒有辦法按時間或者分數高低對,也不能按按電影類型分類查看,因此咱們能夠將上面的數據爬取下來,自行整理。python
在開始安裝scrapy前,咱們在項目路徑下新建個python環境(這是個好習慣,能夠防止安裝環境的衝突):shell
python -m venv ./douban_scrapy_env
source ./douban_scrapy_env/bin/activate
而後執行pip install scrapy
安裝scrapy庫數據庫
如今咱們能夠開始使用scrapy,執行scrapy startproject douban
就會在當前路徑下自動新建項目文件夾douban,該文件夾下包含如下文件:json
douban/
scrapy.cfg
douban/
__init__.py
items.py
middlewares.py
pipelines.py
settings.py
spiders/
__init__.py
複製代碼
其中:瀏覽器
使用scrapy編寫爬蟲,要先定義items,而後再在spiders文件夾下寫爬蟲腳本。可是如今,咱們還一頭霧水,由於咱們還沒去了解咱們要爬取的網頁。下面咱們試試用scrapy的終端調試功能來熟悉咱們要爬取的網頁。markdown
咱們能夠用scrapy shell [url]
的命令打開抓取對應網頁的終端,同時爬取下來的抓取結果response,咱們能夠基於它來調試咱們提取數據的規則。如今咱們試試用scrapy shell https://www.douban.com/doulist/30299/
命令來爬取一下豆瓣高分電影列表第一頁:網絡
能夠看到咱們爬取的結果返回403,這意味着咱們這樣爬取他們的網頁被他們的防爬蟲機制攔截了。對此,咱們試試在命令後加-s USER_AGENT='Mozilla/5.0'
:
咱們成功獲取了正常的爬取結果,這說明對方是經過請求頭信息判斷出咱們是爬蟲腳本,經過改變USER_AGENT來假裝成咱們是經過瀏覽器訪問的。若是咱們不想每次執行命令都加上USER_AGENT參數,咱們能夠在項目文件夾下的setting文件設定USER_AGENT='Mozilla/5.0'
。
如今咱們能夠開始調試爬取規則。咱們知道,response是咱們爬取後的結果對象,當咱們基於此對象創造選擇器後,就能夠經過這個選擇器用scrapy的命令提取咱們須要的內容,創造選擇器有兩種方式:
from scrapy.selector import Selector
response.selector
在scrapy中,咱們能夠對選擇器使用css或xpath方法來提取咱們須要的數據。對於前端有所瞭解的話會知道css是前端定義樣式的語言,而在這裏,選擇器對象中設定了css方法,參數是基於樣式提取數據的規則語句。xpath也是選擇器中定義的用於提取數據的函數,只是xpath的規則語法與css不一樣。本文主要使用xpath方法,下面介紹xpath語法的基本規則(用abc等單一字符代替html元素):
response.selector.xpath("a")
, 提取指定環境下沒有父元素的a元素response.selector.xpath("a/b")
, 提取a元素下的第一級元素bresponse.selector.xpath("//a")
, 提取頁面中全部a元素無論元素位於什麼位置response.selector.xpath("a//b")
, 提取a元素下全部b元素,無論b元素在a元素下第幾級response.selector.xpath("a/@b")
, 提取a元素下的b屬性response.selector.xpath("a/b[1]")
, 提取a元素下第一級b元素中的第一個response.selector.xpath("//a[@b]")
提取全部含有b屬性的a元素response.selector.xpath("//a[@b='c']")
, 提取全部b屬性值爲c的a元素response.selector.xpath("//a[contains("b", "c")]")
, 提取全部擁有b屬性,且b屬性值中包含c字符的a元素 如今咱們認識了xpath的部分語法規則,讓咱們試試提取出頁面列表中全部電影的名字。咱們先執行view(response)
把網頁在瀏覽器打開,而後用瀏覽器的開發者工具模式查看包含電影名的html內容。
根據咱們的觀察,咱們發現:電影列表的每一項都存放在類名爲doulist-item
的div裏,該div下有個類名爲mod的div存放內容,類名爲mod的div下有個類名爲bd doulist-subject
的div存放主體內容,而後該div下類名爲title
的div裏的a標籤就存放着咱們須要的電影名, 這頁面內的電影名大都如此存放(咱們得出如此結論是由於頁面內電影列表裏的每一項樣式都是同樣的),因此我能夠用以下規則提取出頁面中每一個電影的名字:
response.selector.xpath("//div[@class='doulist-item']/div[@class='mod']/div[@class='bd doulist-subject']/div[@class='title']/a/text()").extract()
複製代碼
由於咱們要的是a標籤下的文本內容,因此咱們在a標籤路徑後接text()
代表咱們要這個元素下的文本。xpath函數返回的是由一個個selector對象組成的列表,咱們須要用extract函數,把selector對象轉換爲純文本。
上圖就是咱們提取出的頁面中電影名,由於提取出的文本還帶有一些額外的換行和退格符,因此咱們再對每一項提取的字符串使用strip函數獲得最後提取結果。咱們對結果仔細觀察發現提取的結果裏有空白字符串,按理來講咱們的提取路徑應該是徹底一致的,爲什麼結果裏會偶而穿插字符串,難道是頁面內的電影名處有什麼樣式不同的地方。
咱們看到第二個電影名處比第一個多了一個播放圖標,難道這就是咱們提取到空白字符串的緣由?下面咱們把第一個提取到的第二個包含電影名的selector展開看看:
咱們從圖中看到,提取到的包含電影名的a標籤元素,若是裏面有電影圖標,前面就會有一個包含換行符和空格的字符串,這就是咱們提取到的電影名去除多餘字符後有空白字符串的緣由。當咱們提取到的文本有兩段,這意味着前一段是空白字符串,後一段纔是咱們要的電影名,那麼咱們每次都取最後一段文本就好了,因此咱們能夠把以前的xpath規則再作修改:
response.selector.xpath("//div[@class='doulist-item']/div[@class='mod']/div[@class='bd doulist-subject']/div[@class='title']/a/text()[last()]").extract()
複製代碼
這裏咱們在text()後面加上[last()]代表咱們要的只是最後的文本。此外,由於咱們使用xpath返回的是selector列表,咱們是對selector對象使用xpath函數的,對selector對象列表也能使用。這意味着咱們能夠對xpath返回的結果再次使用xpath進行提取,這樣可讓代碼結構更清晰,以上電影名提取代碼等同:
films_box = response.selector.xpath("//div[@class='doulist-item']/div[@class='mod']")
films_body = films_box.xpath("div[@class='bd doulist-subject']")
films_name = films_body.xpath("div[@class='title']/a/text()[last()]").extract()
複製代碼
最後咱們再總結一下得出用scrapy的xpath提取目標數據規則的流程:
咱們如今分析出了電影列表中電影名字的提取規則,這意味着咱們能夠開始寫爬蟲腳本了。在scrapy中,咱們要寫爬蟲腳本錢,先要定義items,即須要爬取的目標實體。在scrapy中定義的item爲一個個類,類名爲item名,類中直接定義的類變量做爲item的屬性,例如:
import scrapy
class Film(scrapy.item):
name = scrapy.Field()
複製代碼
以上是咱們定義的電影Film的item,具備電影名name這一屬性值。建立的item類繼承scrapy.item,在聲明item屬性時使用scrapy.Field,不須要指定類型。咱們建立item主要目的是使用scrapy的自動保存數據功能,例如把提取結果按咱們定義的item結構導出爲excel文件或保存到數據庫。
咱們知道怎麼定義item以後就能夠開始考慮編寫spider腳本了,編寫spider有如下幾個要點:
import Spider from scrapy
,編寫爬蟲類時須要繼承此類下面咱們編寫豆瓣電影爬蟲腳本:
import scrapy
from douban.items import Film
class DouBan(scrapy.Spider):
name = "douban"
start_urls=[""https://www.douban.com/doulist/30299/""]
def parse(self, response):
films_box = response.selector.xpath("//div[@class='doulist-item']/div[@class='mod']")
films_body = films_box.xpath("div[@class='bd doulist-subject']")
films_name = films_body.xpath("div[@class='title']/a/text()[last()]").extract()
for name in films_name:
name = name.strip()
film = Film()
film["name"] = name
yield film
複製代碼
在以上爬蟲腳本中咱們設置爬蟲的名字爲douban,要爬取的頁面以及在parse函數中設定解析爬取結果並提取須要數據的方法。咱們完成爬蟲腳本後就能夠執行scrapy crawl douban -o 文件名
把提取到的數據保存到文件中,scrapy支持保存的文件類型有'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle'
裏面一開始是不包含excel文件格式的,可是導出爲excel文件是咱們最想要的(利用excel功能直接篩選或者排序電影結果)。
爲此咱們能夠安裝python庫scrapy-xlsx
,它的主要功能就是讓咱們在scrapy項目中能夠把咱們的結果導出爲excel文件。在項目環境下執行pip install scrapy-xlsx
安裝庫,後在項目文件夾下的settings.py文件裏設置:
FEED_EXPORTERS = {
'xlsx': 'scrapy_xlsx.XlsxItemExporter',
}
複製代碼
如今咱們能夠在項目文件夾路徑下執行scrapy crawl douban -o douban_films.xlsx
把結果導出成excel文件
咱們以前是在xpath中使用last()方法只取最後一段文原本過濾空白的電影名,若是咱們遇到沒法使用xpath語法來處理無用文本時,咱們是否須要在spider腳本里進行處理?其實,scrapy提供了咱們方便處理和保存提取結果的方式,名爲pipeline。pipeline意思爲管道,這裏咱們使用pipeline就像是構建新的產品流水線環節,在這流水線上咱們會對產品進行檢測處理,或者將產品如何封裝保存。因此,咱們定義pipeline主要是爲了如下四點:
咱們以前說過項目文件夾下有個pipelines.py文件,咱們就是在這裏定義咱們的pipeline。每一個定義的pipeline爲一個類,類名能夠隨意,可是裏面必需要包含方法process_item(item, spider), 在這裏咱們能夠獲取scrapy提取到的item並進行處理。如今,咱們試試編寫DoubanPipeline,丟失電影名爲空白字符串的item:
from scrapy.exceptions import DropItem
class DoubanPipeline:
def process_item(self, item, spider):
if item["name"]:
return item
else:
raise DropItem("丟棄空白電影名")
複製代碼
如今咱們編寫了一個Pipeline,咱們要讓scrapy使用它,在settings.py裏設置:
ITEM_PIPELINES = {
'douban.pipelines.DoubanPipeline': 300,
}
複製代碼
settings.py裏的ITEM_PIPELINES項能夠定義提取數據時須要使用的pipeline,裏面的key是pipeline的路徑,值是0-1000範圍內的數字,用於決定pipeline的啓用順序。
如今咱們能夠修改一下咱們原來的爬蟲腳本:
def parse(self, response):
films_box = response.selector.xpath("//div[@class='doulist-item']/div[@class='mod']")
films_body = films_box.xpath("div[@class='bd doulist-subject']")
films_name = films_body.xpath("div[@class='title']/a/text()").extract()
for name in films_name:
name = name.strip()
film = Film()
film["name"] = name
yield film
複製代碼
咱們在parse函數裏沒有在xpath語句中使用last(), 也沒有再判斷和過濾空白的電影名,由於咱們已經有了一個用於過濾電影名的pipeline,如今咱們再次執行scrapy crawl douban -o douban_films.xlsx
能夠看到後臺輸出的信息裏顯示,當提取到的電影名爲空白字符串,就會拋棄而且輸出咱們自定義的警告信息,而且最後獲得的輸出文件結果也是和咱們原來的一致。
如今咱們知道如何從頁面內提取結果,可是咱們目標的豆瓣電影列表可不止一頁。爲了爬取全部電影列表數據,咱們須要知道每一頁對應的連接。
咱們觀察了頁面下面帶數字123頁面跳轉標籤看到了他們的每個標籤對應連接,發現每一個連接除了start=後面的數字不同其餘都是一致的,而且每一個連接的start=後面的值以25單調遞增,第一頁電影列表項爲25,因此咱們就知道每個連接start=後面數字應該是頁數乘以25的積。
如今咱們知道了怎麼生成每個要爬取網頁的連接,如今咱們還要知道何時停下。咱們能夠看到在跳轉頁面的標籤裏表明當前頁的span標籤有個屬性值爲data-total-page,這就是咱們所須要的。
咱們知道,當咱們設定了start_urls後,scrapy會爬取裏面全部鏈接而且默認執行parse函數,因此咱們能夠在start_urls裏放置第一頁的連接,而後在parse函數裏提取data-total-page值,而後返回因此要爬取的scrapy.Request對象(帶咱們生成的連接),scrapy,Request對象的回調函數爲parse_page,咱們自定義的用於提取電影信息的函數:
class DouBan(scrapy.Spider):
name = "douban"
start_urls = ["https://www.douban.com/doulist/30299/"]
def parse(self, response):
page_total = response.selector.xpath("//span[@class='thispage']/@data-total-page").extract()
if page_total:
page_total = int(page_total[0])
for i in range(page_total):
url = "https://www.douban.com/doulist/30299/?start={}&sort=seq&playable=0&sub_type=".format(i*25)
yield scrapy.Request(url, callback=self.parse_page)
def parse_page(self, response):
films_box = response.selector.xpath("//div[@class='doulist-item']/div[@class='mod']")
films_body = films_box.xpath("div[@class='bd doulist-subject']")
films_name = films_body.xpath("div[@class='title']/a/text()").extract()
for name in films_name:
name = name.strip()
film = Film()
film["name"] = name
yield film
複製代碼
此外,咱們此次爬取的數據量大,若是持續爬取可能會被對方檢測出來,他們應該也不但願本身的網站被這樣無心義的密集訪問。對此咱們能夠減慢咱們的爬蟲腳本訪問對方數據庫的速度,在項目文件夾中的settings.py文件中設置:
AUTOTHROTTLE_ENABLED = True
DOWNLOAD_DELAY = 3
複製代碼
其中:
AUTOTHROTTLE_ENABLED設置爲True後開啓自動限速,能夠自動調整scrapy併發請求數和下載延遲,AUTOTHROTTLE_ENABLED調整的下載延遲不會低於設置的DOWNLOAD_DELAY
DOWNLOAD_DELAY用於設置下載延遲,即下次下載距上次下載須要等待的時間,單位爲秒
如今咱們知道了如何調試xpath提取數據的路徑、定義item、編寫spider、藉助pipeline處理數據、爬取每一頁。咱們基本上是瞭解瞭如何用scrapy爬蟲,但咱們還有個初始的目標: 把每一個電影的信息(名字、評分、評價人數、導演、主演、類型、製片國家或地區和時間)爬取下來,好方便咱們在線下本身對這些數據進行分類排序以找到咱們本身喜歡看的電影。
在咱們開始寫這些信息的提取路徑以前,咱們先在原來的item類Film裏定義新的信息字段:
class Film(scrapy.Item):
name = scrapy.Field()
score = scrapy.Field()
rating_users = scrapy.Field()
director = scrapy.Field()
starring = scrapy.Field()
film_type = scrapy.Field()
country_regin = scrapy.Field()
year = scrapy.Field()
複製代碼
如今咱們再在shell中調試每一個信息的xpath提取路徑:
films_box = response.selector.xpath("//div[@class='doulist-item']/div[@class='mod']")
films_body = films_box.xpath("div[@class='bd doulist-subject']")
# 電影評分
films_score = films_body.xpath("div[@class='rating']/span[@class='rating_nums']/text()").extract()
# 評價人數
rating_users = films_body.xpath("div[@class='rating']/span[3]/text()").extract()
number_pattern = "[0-9]+"
rating_users = [re.search(number_pattern, i).group() for i in rating_users]
# 電影主要信息
films_abstract = films_body.xpath("div[@class='abstract']")
複製代碼
電影主要信息裏包含了咱們須要的電影導演、主演、類型等信息,如今咱們再在原來的spider腳本進行補充:
...
import re
...
def parse_page(self, response):
films_box = response.selector.xpath("//div[@class='doulist-item']/div[@class='mod']")
films_body = films_box.xpath("div[@class='bd doulist-subject']")
films_name = films_body.xpath("div[@class='title']/a/text()[last()]").extract()
films_score = films_body.xpath("div[@class='rating']/span[@class='rating_nums']/text()").extract()
rating_users = films_body.xpath("div[@class='rating']/span[3]/text()").extract()
number_pattern = "\d+"
rating_users = [re.search(number_pattern, i).group() for i in rating_users]
films_abstract = films_body.xpath("div[@class='abstract']")
for idx, name in enumerate(films_name):
name = name.strip()
film = Film()
film["name"] = name
film["score"] = films_score[idx]
film["rating_users"] = rating_users[idx]
info_list = films_abstract[idx].xpath("text()").extract()
info_list = [i.strip() for i in info_list]
for s in info_list:
if s.startswith("導演"):
film["director"] = s[4:]
elif s.startswith("主演"):
film["starring"] = s[4:]
elif s.startswith("類型"):
film["film_type"] = s[4:]
elif s.startswith("製片國家/地區"):
film["country_regin"] = s[9:]
elif s.startswith("年份"):
film["year"] = s[4:]
yield film
複製代碼
如今咱們再次在項目文件夾路徑下執行scrapy crawl douban -o douban_films.xlsx
就能夠獲得一份包含咱們全部須要信息的excel。
以上就是咱們在結果excel文件中篩選出來的評分大於8.9,以評價人數降序排列後的電影信息,也就是你們一致認爲好看的電影哦。
這篇文章內容到此就要結束了,本文至此主要講了用scrapy的15點內容:
但願這篇文章對你們的爬蟲啓蒙可以有所幫助。