1.scrapy框架介紹
Scrapy是用純Python實現的一個爲了爬取網站數據、提取結構性數據而編寫的應用框架
Scrapy 特色是使用了 Twisted異步網絡框架來處理網絡通訊,加快了下載速度,不用自己去實現異步框架,並且包含了各種中間件接口,可以靈活的完成各種需求
1.1 scrapy框架架構圖
Scrapy Engine(引擎): 負責Spider、ItemPipeline、Downloader、Scheduler中間的通訊,信號、數據傳遞等。
Scheduler(調度器): 它負責接受引擎發送過來的Request請求,並按照一定的方式進行整理排列,入隊,當引擎需要時,交還給引擎。
Downloader(下載器):負責下載Scrapy Engine(引擎)發送的所有Requests請求,並將其獲取到的Responses交還給Scrapy Engine(引擎),由引擎交給Spider來處理,
Spider(爬蟲):它負責處理所有Responses,從中分析提取數據,獲取Item字段需要的數據,並將需要跟進的URL提交給引擎,再次進入Scheduler(調度器),
Item Pipeline(管道):它負責處理Spider中獲取到的Item,並進行進行後期處理(詳細分析、過濾、存儲等)的地方.
Downloader Middlewares(下載中間件):你可以當作是一個可以自定義擴展下載功能的組件。
Spider Middlewares(Spider中間件):你可以理解爲是一個可以自定擴展和操作引擎和Spider中間通信的功能組件(比如進入Spider的Responses;和從Spider出去的Requests)
注意!只有當調度器中不存在任何request了,整個程序纔會停止,(也就是說,對於下載失敗的URL,Scrapy也會重新下載。)
1.2 scrapy安裝
#Windows平臺 1、pip3 install wheel #安裝後,便支持通過wheel文件安裝軟件 3、pip3 install lxml 4、pip3 install pyopenssl 5、下載並安裝pywin32:https://sourceforge.net/projects/pywin32/files/pywin32/ # 根據電腦Python版本和位數下載並安裝最新版的pywin32,它會自動尋找Python的安裝路徑,所以不需要做任何修改,一直單擊【下一步】即可。 # 這裏有時候會報停止工作,但是通過pip3 list 命令可以看到他已經存在 6、下載twisted的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted 7、執行pip3 install 下載目錄路徑\Twisted-17.9.0-cp36-cp36m-win_amd64.whl # Scrapy需要依賴Twisted。Twisted是Python下面一個非常重要的基於事件驅動的IO引擎。Twisted的安裝依賴於pywin32 8、pip3 install scrapy #Linux平臺 1、pip3 install scrapy
# 這裏安裝好pywin32後,後期可能還是無法運行(報ImportError: DLL load failed錯誤),這裏一般都是我們該模塊安裝的有問題導致的,這裏可以參考知乎或者參考Stack Overflow,
我的是參考Stack Overflow下的說明,執行了 pip install win10toast --ignore-installed 纔好的
1.3 命令行相關指令
# 1 查看幫助 scrapy -h scrapy <command> -h # 2 有兩種命令:其中Project-only必須切到項目文件夾下才能執行,而Global的命令則不需要 Global commands: startproject #創建項目 genspider #創建爬蟲程序 settings #如果是在項目目錄下,則得到的是該項目的配置 runspider #運行一個獨立的python文件,不必創建項目 shell #scrapy shell url地址 在交互式調試,如選擇器規則正確與否 fetch #獨立於程單純地爬取一個頁面,可以拿到請求頭 view #下載完畢後直接彈出瀏覽器,以此可以分辨出哪些數據是ajax請求 version #scrapy version 查看scrapy的版本,scrapy version -v查看scrapy依賴庫的版本 Project-only commands: crawl #運行爬蟲,必須創建項目才行,確保配置文件中ROBOTSTXT_OBEY = False check #檢測項目中有無語法錯誤 list #列出項目中所包含的爬蟲名 edit #編輯器,一般不用 parse #scrapy parse url地址 --callback 回調函數 #以此可以驗證我們的回調函數是否正確 bench #scrapy bentch壓力測試 # 3 官網鏈接 https://docs.scrapy.org/en/latest/topics/commands.html
1.4 框架結構
''' project_name/ scrapy.cfg # 項目的主配置信息,用來部署scrapy時使用 project_name/ __init__.py items.py # 設置數據存儲模板,用於結構化數據,如:Django的Model pipelines.py # 數據處理行爲,如:一般結構化的數據持久化 settings.py # 配置文件,如:遞歸的層數、併發數,延遲下載等。強調:配置文件的選項必須大寫否則視爲無效,正確寫法USER_AGENT='xxxx' spiders/ # 爬蟲目錄,如:創建文件,編寫爬蟲規則 __init__.py 爬蟲1.py # 爬蟲程序1 爬蟲2.py 爬蟲3.py '''
1.5 項目流程
新建項目 (scrapy startproject xxx):新建一個新的爬蟲項目 明確目標 (編寫items.py):明確你想要抓取的目標 製作爬蟲 (spiders/xxspider.py):製作爬蟲開始爬取網頁 存儲內容 (pipelines.py):設計管道存儲爬取內容
1.6 啓動一個項目
1 scrapy startproject DianShang # 創建爬蟲項目 2 scrapy genspider jd jd.com # 生成一個爬蟲程序 3 scrapy crawl jd # 運行scrapy項目
現在我們有了基本的項目骨架
1.7 spider類的說明
Spiders是爲站點爬網和解析頁面定義自定義行爲的地方
spiders下的jd.py文件
# -*- coding: utf-8 -*- import scrapy class JdSpider(scrapy.Spider): name = 'jd' allowed_domains = ['jd.com'] start_urls = ['http://jd.com/'] def parse(self, response): pass
Spider類中的start_requests方法:
def start_requests(self): cls = self.__class__ if method_is_overridden(cls, Spider, 'make_requests_from_url'): warnings.warn( "Spider.make_requests_from_url method is deprecated; it " "won't be called in future Scrapy releases. Please " "override Spider.start_requests method instead (see %s.%s)." % ( cls.__module__, cls.__name__ ), ) for url in self.start_urls: yield self.make_requests_from_url(url) else: for url in self.start_urls: yield Request(url, dont_filter=True)
有時候後臺的start_requests方法會訪問不到我們的目標站點,我們大多數情況需要自己構造此方法,它的最終返回值是用生成器yield返回的,我們自己寫也建議使用生成器。
start_requests方法它默認的回調函數就是parse
2. 創建項目
我們的目標是爬取亞馬遜商城iphoex的名稱,價格以及配送方,注意的是:我們需要的這些信息都在手機詳情頁面,而在手機列表頁面只有我們點擊它的圖片或者文字纔會看到手機詳細信息
想要獲取手機的信息,現在我們需要進行分佈爬取,第一次先獲取每一個手機詳情頁面的url,可以通過手機列表頁面的圖片進行獲取,也能通過文字獲取,然後通過二次解析去拿到我們需要的信息
拿到信息後,通過MongoDB對我們的信息做持久化處理
2.1 創建項目文件
爬蟲Scrapy命令: 1 scrapy startproject Amazon # 創建爬蟲項目 2 scrapy genspider amazon amazon.cn # 生成一個爬蟲程序 3 scrapy crawl amazon # 運行scrapy項目
創建好後 spiders下自動生成的amazon.py文件
# -*- coding: utf-8 -*- import scrapy class AmazonSpider(scrapy.Spider): name = 'amazon' allowed_domains = ['amazon.cn'] start_urls = ['http://amazon.cn/'] def parse(self, response): pass
2.2 創建啓動文件
創建好後,它默認只能在終端運行,我們可以在它的根目錄下創建一個bin.py文件,來作爲它的執行文件
from scrapy.cmdline import execute execute(["scrapy","crawl","amazon",'--nolog']) # 不打印日誌信息
如果不需要相關日誌信息,,可以在列表後面追加一個參數:'--nolog'
2.3 關閉ROBOTSTXT_OBEY命令
關閉setting下的ROBOTSTXT_OBEY命令,該命令的作用是讓你遵循爬蟲協議的情況下爬取相關內容,我們爲了避免它對我們爬取時的影響,可以把他修改爲False
2.4 獲取商品信息
獲取商品列表的詳情鏈接
amazon.py
import scrapy from scrapy import Request # 導入模塊 class AmazonSpider(scrapy.Spider): name = 'amazon' allowed_domains = ['amazon.cn'] # 自定義配置,在Spider中custom_settings設置的是None custom_settings = { 'REQUEST_HEADERS': { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', } } def start_requests(self): r1 = Request( url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphonex", headers=self.settings.get('REQUEST_HEADERS'), ) yield r1 def parse(self, response): # 獲取商品名 # detail_urls = response.xpath('//*[@id="result_0"]/div/div[3]/div[1]/a/h2').extract() # 商品單個商品詳情鏈接 # detail_urls = response.xpath('//*[@id="result_0"]/div/div[3]/div[1]/a/@href').extract() # 獲取整個頁面商品詳情鏈接 # detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract() detail_urls = response.xpath('//*[starts-with(@id,"result")]/div/div[3]/div[1]/a/@href').extract() print(detail_urls)
現在讓scrapy去訪問這些鏈接,只要parse函數返回一個Request對象,它就會放到異步請求列表裏面,並由twisted發送異步請求
import scrapy from scrapy import Request # 導入模塊 class AmazonSpider(scrapy.Spider): name = 'amazon' allowed_domains = ['amazon.cn'] # 自定義配置,在Spider中custom_settings設置的是None custom_settings = { 'REQUEST_HEADERS': { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', } } def start_requests(self): r1 = Request( url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphonex", headers=self.settings.get('REQUEST_HEADERS'), ) yield r1 def parse(self, response): # 獲取商品名 # detail_urls = response.xpath('//*[@id="result_0"]/div/div[3]/div[1]/a/h2').extract() # 商品單個商品詳情鏈接 # detail_urls = response.xpath('//*[@id="result_0"]/div/div[3]/div[1]/a/@href').extract() # 獲取整個頁面商品詳情鏈接 # detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract() detail_urls = response.xpath('//*[starts-with(@id,"result")]/div/div[3]/div[1]/a/@href').extract() for url in detail_urls: yield Request(url=url, headers=self.settings.get('REQUEST_HEADERS'), # 請求頭 callback=self.parse_detail, # 回調函數 dont_filter=True # 不去重 ) def parse_detail(self, response): # 獲取商品詳細信息 # 商品名,獲取第一個結果 name = response.xpath('//*[@id="productTitle"]/text()').extract_first() if name: name = name.strip() # 商品價格 price = response.xpath('//*[@id="priceblock_ourprice"]/text()').extract_first() # 配送方式 delivery = response.xpath('//*[@id="ddmMerchantMessage"]/*[1]/text()').extract_first() print(name, price, delivery)
2.5 存儲商品信息到MongoDB
我們需要使用items.py文件
import scrapy # 獲取你想要的字段 class AmazonItem(scrapy.Item): # define the fields for your item here name = scrapy.Field() price= scrapy.Field() delivery=scrapy.Field()
並修改amazon.py最後的數據返回值
amazon.py
import scrapy from scrapy import Request # 導入模塊 from Amazon.items import AmazonItem class AmazonSpider(scrapy.Spider): name = 'amazon' allowed_domains = ['amazon.cn'] # 自定義配置,在Spider中custom_settings設置的是None custom_settings = { 'REQUEST_HEADERS': { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', } } def start_requests(self): r1 = Request( url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphonex", headers=self.settings.get('REQUEST_HEADERS'), ) yield r1 def parse(self, response): # 獲取商品名 # detail_urls = response.xpath('//*[@id="result_0"]/div/div[3]/div[1]/a/h2').extract() # 商品單個商品詳情鏈接 # detail_urls = response.xpath('//*[@id="result_0"]/div/div[3]/div[1]/a/@href').extract() # 獲取整個頁面商品詳情鏈接 # detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract() detail_urls = response.xpath('//*[starts-with(@id,"result")]/div/div[3]/div[1]/a/@href').extract() for url in detail_urls: yield Request(url=url, headers=self.settings.get('REQUEST_HEADERS'), # 請求頭 callback=self.parse_detail, # 回調函數 dont_filter=True # 不去重 ) def parse_detail(self, response): # 獲取商品詳細信息 # 商品名,獲取第一個結果 name = response.xpath('//*[@id="productTitle"]/text()').extract_first() if name: name = name.strip() # 商品價格 price = response.xpath('//*[@id="priceblock_ourprice"]/text()').extract_first() # 配送方式 delivery = response.xpath('//*[@id="ddmMerchantMessage"]/*[1]/text()').extract_first() # 生成標準化數據 item = AmazonItem() # 實例化 # 增加鍵值對 item["name"] = name item["price"] = price item["delivery"] = delivery return item # 返回的是一個字典
處理Spider中獲取到的Item ,需要PipeLine將數據儲存到MongoDB中
pipelines.py
from pymongo import MongoClient class MongodbPipeline(object): def __init__(self, host, port, db, table): self.host = host self.port = port self.db = db self.table = table @classmethod def from_crawler(cls, crawler): """ Scrapy會先通過getattr判斷我們是否自定義了from_crawler,有則調它來完 成實例化 """ HOST = crawler.settings.get('HOST') PORT = crawler.settings.get('PORT') DB = crawler.settings.get('DB') TABLE = crawler.settings.get('TABLE') return cls(HOST, PORT, DB, TABLE) def open_spider(self, spider): """ 爬蟲剛啓動時執行一次 """ # self.client = MongoClient('mongodb://%s:%s@%s:%s' %(self.user,self.pwd,self.host,self.port)) self.client = MongoClient(host=self.host, port=self.port) def close_spider(self, spider): """ 爬蟲關閉時執行一次 """ self.client.close() def process_item(self, item, spider): # 操作並進行持久化 d = dict(item) if all(d.values()): self.client[self.db][self.table].insert(d) print("添加成功一條")
修改settings.py,在下面增加MongoDB連接信息
# MongoDB連接信息 HOST="127.0.0.1" PORT=27017 DB="amazon" # 數據庫名 TABLE="goods" # 表名
同時開啓MongoDB的PipeLine信息,注意這裏開啓後還需要進行修改,我們pipelines下的名稱是MongodbPipeline
ITEM_PIPELINES = { 'Amazon.pipelines.MongodbPipeline': 300, }
此時在cmd下啓動我們的mongodb(mongod),進入我們的數據庫(mongo),並自行創建數據庫
執行我們的bin文件,此時會在你會發現數據都存儲進我們的數據庫內