目標確定
本人在做一個前端頁面的項目,用到一些電影數據 , 就打算在網上爬取一些數據,之前使用自寫的爬蟲demo,但效果不太好,而且費時間.所以打算用框架解決.
框架選擇Scrapy.
爬取網頁:https://www.ygdy8.net/html/gndy/china/index.html
頁面分析
打開頁面,https://www.ygdy8.net/html/gndy/china/index.html
進入開發者工具查看元素,我們需要電影名與電影分類.這個頁面可以滿足要求.
我們還需要圖片,這個頁面裏並沒有,我們打開一個鏈接,發現了圖片,頁面分析結束.
頁面分析結束
準備工作
安裝 Scrapy 與安裝其他 Python 包沒有區別,同樣使用如下命令來安裝:
pip install scrapy
在安裝過程中,可能會出現在安裝 Twisted 時會提示以下錯誤:
error:Microsoft Visual C++ 14.0 is required. Get it with “Microsoft Visual C++ Build Tools”:http://landinghub.visualstudio.com/visual-cpp-build-tools
提示上面的錯誤只是因爲 pip 自動下載的 Twisted 安裝包有一些缺陷,因此可以先自行下載 Twisted 安裝包。進入 www.lfd.uci.edu/~gohlke/pythonlibs/ 站點,在該頁面中間查找“Twisted”項目,可以看到如圖 1 所示的下載鏈接。
當前 Twisted 的最新版是 19.2.1,Twisted 爲 2.7、3.5、3.6、3.7 等不同版本的 Python 提供了對應的安裝包。由於本教程內容主要以 Python 3.6 爲主,因此應該下載 Twisted 的 Python 3.6 版本,其中文件名帶 win32 的是 32 位版本,而帶 win_amd64 的則是 64 位版本,此處還需要根據操作系統的位數選擇對應的版本。
在下載了合適的 Twisted 安裝包後,會得到一個 Twisted-18.9.0-cp36-cp36m-win_amd64.whl 文件(針對 64 位系統的),該文件就是 Twisted 安裝包。
運行如下命令來安裝 Twisted 包。
pip install Twisted-19.2.1-cp36-cp36m-win_amd64.whl
在安裝過程中會自動檢查,如有必要,會自動下載並安裝 Twisted 所依賴的第三方包,如 zope.interface、Automat、incremental 等。
在安裝完成後,會提示如下安裝成功的信息:
Successfully installed Twisted-18.9.0
在成功安裝 Twisted 包之後,再次執行 pip install scrapy 命令,即可成功安裝 Scrapy。在安裝成功後,會顯示如下提示信息:
Successfully installed Scrapy-1.6.1
如果後面執行時還是報 No module named win32api 錯誤 ,請執行.
pip install pypiwin32
scrapy shell
下面將會使用 Scrapy 提供的 shell 調試工具來抓取該頁面中的信息。使用如下命令來開啓 shell 調試:
scrapy shell https://www.ygdy8.net/html/gndy/china/index.html
運行上面命令,將會看到 Scrapy 並未抓取到頁面數據,但有些運用反爬蟲的頁面會返回了 403 錯誤,不允許使用 Scrapy“爬取”數據。爲了解決這個問題,我們需要讓 Scrapy 僞裝成瀏覽器。
我們可以加上瀏覽器頭來僞裝瀏覽器訪問.
scrapy shell -s USER_AGENT='Mozilla/5.0' https://www.ygdy8.net/html/gndy/china/index.html
接下來就可以使用 XPath 或 css 選擇器來提取我們感興趣的信息了。
Xpath是一個簡單方便的選擇器.這裏簡單的介紹一下它的語法.
表達式 | 作用 |
---|---|
nodename | 匹配此節點的所有內容 |
/ | 匹配根節點 |
// | 匹配任意位置的節點 |
. | 匹配當前節點 |
. . | 匹配父節點 |
@ | 匹配屬性 |
典型的,比如可以使用 //div 來匹配頁面中任意位置處的 <div…/> 元素,也可以使用 //div/span 來匹配頁面中任意位置處的 <div…> 元素內的 <span…/> 子元素。
XPath 還支持“謂詞”,就是在節點後增加一個方括號,在方括號內放一個限制表達式對該節點進行限制。
典型的,我們可以使用 //div[@class]來匹配頁面中任意位置處、有 class 屬性的 <div…/> 元素,也可以使用 //div/span[1] 來匹配頁面中任意位置處的 <div…/> 元素內的第一個 <span…/> 子元素;使用 //div/span[last()] 來匹配頁面中任意位置處的 <div…/> 元素內的最後一個 <span…/> 子元素;使用 //div/span[last()-1] 來匹配頁面中任意位置處的 <div…/> 元素內的倒數第二個 <span…/> 子元素.
string-length(text())!=6
以頁面爲例
我們發現每個標籤內容都在table中 , 所以開頭爲://table[@class=“tbspan”]
然後依次向下寫//table[@class=“tbspan”]/tr/td/b/a
到這裏我們發現了兩個 <a>
標籤我們需要第二個
所以後面寫 //table[@class=“tbspan”]/tr/td/b/a[2] 即可獲取標籤對象
我們可以通過text()方法獲取便籤內的文本.也可以通過@href獲取它的鏈接地址
輸入
response.xpath ('(//table[@class="tbspan"]/tr/td/b/a[2]/text())').extract()
輸入
response.xpath ('(//table[@class="tbspan"]/tr/td/b/a[2]@href)').extract()
開始建立scrapy項目
scrapy startproject MovieSpider
在上面命令中,scrapy 是Scrapy 框架提供的命令;startproject 是 scrapy 的子命令,專門用於創建項目;MovieSpider 就是要創建的項目名。
運行結果:
此時在當前目錄下就可以看到一個 MovieSpider 目錄,該目錄就代表 MovieSpider 項目。
查看 MovieSpider 項目,可以看到如下文件結構:
MovieSpider
├── MovieSpider
│ ├── __init__.py
│ ├── items.py
│ ├── middlewares.py
│ ├── pipelines.py
│ ├── __pycache__
│ ├── settings.py
│ └── spiders
│ ├── __init__.py
│ └── __pycache__
└── scrapy.cfg
下面大致介紹這些目錄和文件的作用:
- scrapy.cfg:項目的總配置文件,通常無須修改。
- MovieSpider:項目的 Python 模塊,程序將從此處導入 Python 代碼。
- MovieSpider/items.py:用於定義項目用到的 Item 類。Item 類就是一個 DTO(數據傳輸對象),通常就是定義 N 個屬性,該類需要由開發者來定義。
- MovieSpider/pipelines.py:項目的管道文件,它負責處理爬取到的信息。該文件需要由開發者編寫。
- MovieSpider/settings.py:項目的配置文件,在該文件中進行項目相關配置。
- MovieSpider/spiders:在該目錄下存放項目所需的蜘蛛,蜘蛛負責抓取項目感興趣的信息。
通過前面的 Scrapy shell 調試,已經演示了使用 XPath 從 HTML 文檔中提取信息的方法,只要將這些調試的測試代碼放在 Spider 中,即可實現真正的 Scrapy 爬蟲。
基於 Scrapy 項目開發爬蟲大致需要如下幾個步驟:
定義 Item 類。該類僅僅用於定義項目需要爬取的 N 個屬性。
編輯 items.py
import scrapy
class MoviespiderItem(scrapy.Item):
#電影名
name = scrapy.Field()
#uri
uri = scrapy.Field()
上面程序中,第 2 行代碼表明所有的 Item 類都需要繼承 scrapy.Item 類,接下來就爲所有需要爬取的信息定義對應的屬性,每個屬性都是一個 scrapy.Field 對象。
該 Item 類只是一個作爲數據傳輸對象(DTO)的類,因此定義該類非常簡單。
編寫 Spider 類
應該將該 Spider 類文件放在 spiders 目錄下。這一步是爬蟲開發的關鍵,需要使用 XPath 或 CSS 選擇器來提取 HTML 頁面中感興趣的信息。
Scrapy 爲創建 Spider 提供了 scrapy genspider 命令,該命令的語法格式如下:
scrapy genspider [options] <name> <domain>
在命令行窗口中進入 ZhipinSpider 目錄下,然後執行如下命令即可創建一個 Spider:
scrapy genspider job_position "ygdy8.net"
運行上面命令,即可在 MovieSpider 項目的 MovieSpider/spider 目錄下找到一個 job_position.py 文件
編輯job_position.py
import scrapy
from MovieSpider.items import MoviespiderItem
class JobPositionSpider(scrapy.Spider):
name = 'job_position'
allowed_domains = ['ygdy8.net']
#這裏寫上你要爬取的頁面
start_urls = ['https://www.ygdy8.net/html/gndy/china/index.html']
#爬取的方法
def parse(self, response):
#注意在上面導入MoviespiderItem包
item = MoviespiderItem()
#匹配
for jobs_primary in response.xpath('//table[@class="tbspan"]'):
item['name'] = jobs_primary.xpath ('./tr/td/b/a[2]/text()').extract()
item['uri'] = jobs_primary.xpath ('./tr/td/b/a[2]/@href').extract()
#不能使用return
yield item
修改pipeline類
這個類是對爬取的文件最後的處理,一般爲負責將所爬取的數據寫入文件或數據庫中.
這裏我們將它輸出到控制檯.
class MoviespiderPipeline(object):
def process_item(self, item, spider):
print("name:",item['name'])
print("url:",item['url'])
修改settings類
BOT_NAME = 'MovieSpider'
SPIDER_MODULES = ['MovieSpider.spiders']
NEWSPIDER_MODULE = 'MovieSpider.spiders'
ROBOTSTXT_OBEY = True
# 配置默認的請求頭
DEFAULT_REQUEST_HEADERS = {
"User-Agent" : "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0",
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
}
# 配置使用Pipeline
ITEM_PIPELINES = {
'MovieSpider.pipelines.MoviespiderPipeline': 300,
}
一個 Scarpy項目的簡單架構就完成了我們可以運行一下試試.
在MovieSpider目錄下執行
scrapy crawl job_position
可以看到運行結果
但只有 一頁的內容 , 我們可以解析下一頁 .
將以下代碼加到 job_position.py
new_links = response.xpath('//a[text()="下一頁"]/@href').extract()
if new_links and len(new_links) > 0:
# 獲取下一頁的鏈接
new_link = new_links[0]
# 再次發送請求獲取下一頁數據
yield scrapy.Request("https://www.ygdy8.net/html/gndy/china/" + new_link, callback=self.parse)
job_position.py最終內容
# -*- coding: utf-8 -*-
import scrapy
from MovieSpider.items import MoviespiderItem
class JobPositionSpider(scrapy.Spider):
name = 'job_position'
allowed_domains = ['ygdy8.net']
start_urls = ['https://www.ygdy8.net/html/gndy/china/index.html']
def parse(self, response):
item = MoviespiderItem()
for jobs_primary in response.xpath('//table[@class="tbspan"]'):
item['name'] = jobs_primary.xpath ('./tr/td/b/a[2]/text()').extract()
item['uri'] = jobs_primary.xpath ('./tr/td/b/a[2]/@href').extract()
yield item
new_links = response.xpath('//a[text()="下一頁"]/@href').extract()
if new_links and len(new_links) > 0:
# 獲取下一頁的鏈接
new_link = new_links[0]
# 再次發送請求獲取下一頁數據
yield scrapy.Request("https://www.ygdy8.net/html/gndy/china/" + new_link, callback=self.parse)
再次執行 , 就會一頁一頁的爬取 .
進階
我們可以將爬取到的內入保存到本地.這裏使用python的pandas模塊
我們只需修改pipeline.py的內容即可
class MoviespiderPipeline(object):
def __init__(self):# 定義構造器,初始化要寫入的文件
self.mypd = pd.DataFrame(columns=['name','uri'])
def close_spider(self, spider):#重寫close_spider回調方法
self.mypd.to_csv("movie.csv")
def process_item(self, item, spider):#添加數據到pandas中
self.mypd = self.mypd.append({'name':item['name'][0],'uri':item['uri'][0]},ignore_index=True)
print(len(self.mypd))
這樣就把數據爬取到文件中了, 同理把數據爬取到數據庫什麼的都是類似的 .
進階 圖片爬取
我們在上面獲得了電影名和電影詳情的路徑 , 接下來只需要將每個詳情頁面的圖片爬取出來.
分析頁面:
我們可以很簡單的得到圖片的路徑
我們可以選擇保存圖片路徑用其他下載器下載也可以用自帶的圖片文件選擇器 , 我這裏使用自帶的下載器.
建立名爲 ImageSpider 的Scrapy項目 , 使用自帶的下載器需要修改一些內容 所以我們先修改settings .py
settings. py
BOT_NAME = 'ImageSpider'
SPIDER_MODULES = ['ImageSpider.spiders']
NEWSPIDER_MODULE = 'ImageSpider.spiders'
ROBOTSTXT_OBEY = True
ITEM_PIPELINES = {
'scrapy.pipelines.images.ImagesPipeline': 1,
'scrapy.pipelines.files.FilesPipeline': 2,
}
FILES_STORE = 'D:' # 文件存儲路徑
IMAGES_STORE = 'D:' # 圖片存儲路徑
# 避免下載最近90天已經下載過的文件內容
FILES_EXPIRES = 90
# 避免下載最近90天已經下載過的圖像內容
IMAGES_EXPIRES = 30
# 設置圖片縮略圖
# 圖片過濾器,最小高度和寬度,低於此尺寸不下載
IMAGES_MIN_HEIGHT = 110
IMAGES_MIN_WIDTH = 110
# 瀏覽器頭
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent':"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
}
IMAGES_STORE='D:\\jiandan'
ITEM_PIPELINES = {
'ImageSpider.pipelines.ImagespiderPipeline': 300,
}
items. py
mport scrapy
class ImagespiderItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
image_urls = scrapy.Field()#圖片的鏈接
images = scrapy.Field()
job_position. py
# -*- coding: utf-8 -*-
import scrapy
import pandas as pd
from ImageSpider.items import ImagespiderItem
class JobPositionSpider(scrapy.Spider):
name = 'job_position'
allowed_domains = ['ygdy8.net']
start_urls = ['https://www.ygdy8.net/html/gndy/dyzz/20190713/58833.html']
def parse(self, response):
item = ImagespiderItem()
item['image_urls'] = response.xpath ('//p/img[1]/@src').extract()
yield item
pipelines. py
import scrapy
from scrapy.exceptions import DropItem
from scrapy.pipelines.images import ImagesPipeline #內置的圖片管道
class ImagespiderPipeline(ImagesPipeline):
def get_media_requests(self, item, info):
for image_url in item['image_urls']:
print("圖片連接:",image_url)
yield scrapy.Request(image_url)
def item_completed(self, results, item, info):
image_paths = [x['path'] for ok, x in results if ok]
if not image_paths:
raise DropItem("Item contains no images")
return item
- get_media_requests(item, info)
在工作流程中,管道會得到圖片的URL並從項目中下載。爲了這麼做,你需要重寫 get_media_requests() 方法,並對各個圖片URL返回一個Request
這些請求將被管道處理,當它們完成下載後,結果將以2元素的元組列表形式傳送到 item_completed() 方法: 每個元組包含 (success, file_info_or_error):
success 是一個布爾值,當圖片成功下載時爲 True ,因爲某個原因下載失敗爲False
file_info_or_error 是一個包含下列關鍵字的字典(如果成功爲 True )或者出問題時爲 Twisted Failure 。
url - 文件下載的url。這是從 get_media_requests() 方法返回請求的url。
path - 圖片存儲的路徑(類似 IMAGES_STORE)
checksum - 圖片內容的 MD5 hash
item_completed() 接收的元組列表需要保證與 get_media_requests() 方法返回請求的順序相一致。下面是 results 參數的一個典型值:
[(True,
{‘checksum’: ‘2b00042f7481c7b056c4b410d28f33cf’,
‘path’: ‘full/0a79c461a4062ac383dc4fade7bc09f1384a3910.jpg’,
‘url’: ‘http://www.example.com/files/product1.jpg’}),
(False,
Failure(…))]
該方法 必須返回每一個圖片的URL。
- item_completed(results, items, info)
當一個單獨項目中的所有圖片請求完成時,例如,item裏面一共有10個URL,那麼當這10個URL全部下載完成以後,ImagesPipeline.item_completed() 方法將被調用。默認情況下, item_completed() 方法返回item。.
總之 , 一個圖片爬蟲寫好了 我們可以運行一下試一試 , 可以在圖片保存路徑發現圖片 .
但這只是一張圖片 我們 需要的是剛剛怕下來的movie.csv中全部的圖片 ,那只需要修改 job_position. py
import scrapy
import pandas as pd
from ImageSpider.items import ImagespiderItem
class JobPositionSpider(scrapy.Spider):
name = 'job_position'
allowed_domains = ['ygdy8.net']
start_urls = ['https://www.ygdy8.net/html/gndy/dyzz/20190713/58833.html']#這句話可寫可不寫 , 沒有影響
def start_requests(self):#重寫這個方法
url = pd.read_csv("movie.csv",usecols=["uri"])#讀取csv文件,改爲你的路徑
url = url.head(5)#只取前5條數據
urls=[x for x in url["uri"]]
for url in urls:
url='https://www.ygdy8.net/'+url
yield scrapy.Request(url=url, callback=self.parse)
def parse(self, response):
item = ImagespiderItem()
item['image_urls'] = response.xpath ('//p/img[1]/@src').extract()
yield item
這樣 就完成了整個項目 .