python的scrapy框架使用例項(以糗事百科為例)和xpath的使用
阿新 • • 發佈:2020-08-05
這篇部落格主要是講一下scrapy框架的使用,對於糗事百科爬取資料並未去專門處理
最後爬取的資料儲存為json格式
一、先說一下pyharm怎麼去看一些函式在原始碼中的程式碼實現
按著ctrl然後點選函式就行了
先給出專案的目錄:
二、先說一下setting.py檔案中一些變數的含義
BOT_NAME = 'qsbk' # 定義一下這個專案的根 # 以後想要把這個專案某一個檔案中的某個內容匯入到其他檔案,就可以以“qsbk.檔名”來實現 # 例如: # from qsbk.items import QsbkItem # 這個意思就是將qsbk目錄下的items.py檔案中的QsbkItem類匯入到此檔案中
# 如果它的值為True,那麼網站上沒有爬蟲協議,那麼程式就會什麼都不執行就停止執行 ROBOTSTXT_OBEY = False
DOWNLOAD_DELAY = 3 # 下載延遲開啟,一方面防止ip被封,另一方面也不要給網站伺服器太大壓力
# 設定預設請求頭 DEFAULT_REQUEST_HEADERS = { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en', 'user-agent': '' }
ITEM_PIPELINES = {'qsbk.pipelines.QsbkPipeline': 300, # 通過管道輸出資料,不解除這個註釋,就不會執行pipelines.py一部分程式碼 # 值越小,優先順序越高 }
二、start.py檔案
因為每次執行scrapy都需要在命令列下執行,用慣了pycharm之後看著不太習慣,所以這個檔案就是代替在命令列下輸入執行爬蟲命令,它的執行結果也會在pycharm上顯示,這樣就不用每次開啟cmd去執行scrapy的爬蟲程式了
start.py內容:
from scrapy import cmdline# qsbk_spider是你的爬蟲程式檔案,注意要把這個命令用split拆開 cmdline.execute("scrapy crawl qsbk_spider".split())
三、Item.py檔案內容
import scrapy class QsbkItem(scrapy.Item): # 這個是固定寫法,也就是給QsbkItem類定義兩個變數 # Item是爬蟲爬取到資料後,傳遞給管道去儲存資料的單位。傳遞給管道pipelines.py的Item也可以是字典形式 # 可以自己定義字典,最後返回這個字典也可以,(返回什麼資料可以根據自己喜好,你也可以返回列表) author = scrapy.Field() content = scrapy.Field()
四、pipelines.py檔案
我們使用scrapy內建類來完成匯出資料,可見這個類可以自動把item變成字典,所以我們就不用去多處理
同時在這個檔案中有三個函式,其中兩個open_spider和close_spider檔案中沒有給我們定義,我們需要自己定義。這兩個函式一個在爬蟲開始時執行,一個在爬蟲執行結束時執行
檔案程式碼:
''' 下面給出兩種不同型別的內建儲存資料方式,請根據需求使用 當然也可以不使用內建函式來儲存資料,可以自己寫 ''' # from scrapy.exporters import JsonItemExporter # 每次會把資料放在記憶體,最後在一次性寫入檔案,儲存的是一個josn型別的檔案 # 壞處是比較耗費記憶體 # class QsbkPipeline: # def __init__(self): # self.fp = open('duanzi.json','wb') # self.exporter = JsonItemExporter(self.fp,ensure_ascii=False,encoding='utf-8') # self.exporter.start_exporting() # # def open_spider(self): # print('爬蟲開始了') # def process_item(self, item, spider): # self.exporter.export_item(item) # return item # # def close_spider(self): # self.exporter.finish_exporting() # self.fp.close() # print('爬蟲結束了') from itemadapter import ItemAdapter # 每次呼叫export_item就會把itrm存放在磁碟,壞處是每一次字典是一行,整個檔案不是一個字典,好處是 # 每次寫入磁碟,不會佔用大量記憶體 from scrapy.exporters import JsonLinesItemExporter class QsbkPipeline: def __init__(self): self.fp = open('duanzi.json','wb') self.exporter = JsonLinesItemExporter(self.fp,ensure_ascii=False,encoding='utf-8') # self.exporter.start_exporting() def open_spider(self,spider): # 爬蟲開始的時候會執行 print('爬蟲開始了') def process_item(self, item, spider): # 當有item輸出傳來的時候會執行 # 這樣的話我們就不需要先將item轉換為字典形式再去儲存,我們可以直接傳給匯出器exporter self.exporter.export_item(item) # 只有下面return了,我們才可以對下一個item進行操作 return item def close_spider(self,spider): # 爬蟲結束會執行 # self.exporter.finish_exporting() self.fp.close() print('爬蟲結束了')
五、qsbk_spider檔案
import scrapy from scrapy.http.response.html import HtmlResponse from scrapy.selector.unified import SelectorList from qsbk.items import QsbkItem class QsbkSpiderSpider(scrapy.Spider): name = 'qsbk_spider' # 同一類爬蟲名字要相同,以便於命令列下執行 allowed_domains = ['qiushibaike.com'] # 控制爬蟲範圍,以訪爬蟲通過其他連結,爬到其他網站 # start_url可以傳一個url也可以多個,這個url會傳給引擎,然後引擎會傳給排程器,排程器又會處理後傳到 # 引擎,之後引擎傳給下載器,下載器將資料又通過引擎傳遞到爬蟲的parse函式,它的引數response就是返回的資料 start_urls = ['https://www.qiushibaike.com/text'] base_url = 'https://www.qiushibaike.com' def parse(self, response): # selectorlist duanzidivs = response.xpath('//*[@id="content"]/div/div[2]/div') #獲取包含所有段子的div for duanzidiv in duanzidivs: # selector # get()方法回去的是selector的第一個文字,get_all()獲取的是所有文字 author = duanzidiv.xpath(".//div[1]/a[2]/h2/text()").get().strip() content = duanzidiv.xpath(".//a[1]/div/span[1]/text()").getall() # getall方法會將所有文字資訊轉變成一個佇列,然後轉換成一個欄位 content = ".".join(content).strip() # strip方法就是消除content字串前後行 item = QsbkItem(author=author,content=content) yield item # 讓這個函式變成一個生成器函式 # 找到下一頁按鈕,並提取出裡面href的內容,之後連結拼接形成新的url next_url = response.xpath('//*[@id="content"]/div/div[2]/ul/li[8]/a/@href').get() if not next_url: return else: yield scrapy.Request(self.base_url+next_url,callback=self.parse)
六、xpath的簡單使用
1、選取節點
表示式 | 描述 | 例項 | |
nodename | 選取nodename節點的所有子節點 | xpath(‘//div’) | 選取了div節點的所有子節點 |
/ | 從根節點選取 | xpath(‘/div’) | 從根節點上選取div節點 |
// | 選取所有的當前節點,不考慮他們的位置 | xpath(‘//div’) | 選取所有的div節點 |
. | 選取當前節點 | xpath(‘./div’) | 選取當前節點下的div節點 |
.. | 選取當前節點的父節點 | xpath(‘..’) | 回到上一個節點 |
@ | 選取屬性 | xpath(’//@calss’) | 選取所有的class屬性 |
2、嵌在方括號內,用來查詢某個特定的節點或包含某個制定的值的節點
表示式 | 結果 |
xpath(‘/body/div[1]’) | 選取body下的第一個div節點 |
xpath(‘/body/div[last()]’) | 選取body下最後一個div節點 |
xpath(‘/body/div[last()-1]’) | 選取body下倒數第二個div節點 |
xpath(‘/body/div[positon()<3]’) | 選取body下前兩個div節點 |
xpath(‘/body/div[@class]’) | 選取body下帶有class屬性的div節點 |
xpath(‘/body/div[@class=”main”]’) | 選取body下class屬性為main的div節點 |
xpath(‘/body/div[price>35.00]’) | 選取body下price元素值大於35的div節點 |
3、萬用字元
表示式 | 結果 |
xpath(’/div/*’) | 選取div下的所有子節點 |
xpath(‘/div[@*]’) | 選取所有帶屬性的div節點 |
4、相對路徑查詢
軸名稱 | 表示式 | 描述 |
ancestor | xpath(‘./ancestor::*’) | 選取當前節點的所有先輩節點(父、祖父) |
ancestor-or-self | xpath(‘./ancestor-or-self::*’) | 選取當前節點的所有先輩節點以及節點本身 |
attribute | xpath(‘./attribute::*’) | 選取當前節點的所有屬性 |
child | xpath(‘./child::*’) | 返回當前節點的所有子節點 |
descendant | xpath(‘./descendant::*’) | 返回當前節點的所有後代節點(子節點、孫節點) |
following | xpath(‘./following::*’) | 選取文件中當前節點結束標籤後的所有節點 |
following-sibing | xpath(‘./following-sibing::*’) | 選取當前節點之後的兄弟節點 |
parent | xpath(‘./parent::*’) | 選取當前節點的父節點 |
preceding | xpath(‘./preceding::*’) | 選取文件中當前節點開始標籤前的所有節點 |
preceding-sibling | xpath(‘./preceding-sibling::*’) | 選取當前節點之前的兄弟節點 |
self | xpath(‘./self::*’) | 選取當前節點 |
5、模糊查詢
函式 | 用法 | 解釋 |
starts-with | xpath(‘//div[starts-with(@id,”ma”)]‘) | 選取id值以ma開頭的div節點 |
contains | xpath(‘//div[contains(@id,”ma”)]‘) | 選取id值包含ma的div節點 |
and | xpath(‘//div[contains(@id,”ma”) and contains(@id,”in”)]‘) | 選取id值包含ma和in的div節點 |
text() | xpath(‘//div[contains(text(),”ma”)]‘) | 選取節點文字包含ma的div節點 |