scrapy框架爬取虎撲論壇球隊新聞
用python爬蟲scrapy框架爬取虎撲論壇的30支球隊新聞
Scrapy 框架
-
Scrapy是用純Python實現一個為了爬取網站資料、提取結構性資料而編寫的應用框架,用途非常廣泛。
-
框架的力量,使用者只需要定製開發幾個模組就可以輕鬆的實現一個爬蟲,用來抓取網頁內容以及各種圖片,非常之方便。
-
Scrapy 使用了 Twisted
['twɪstɪd]
(其主要對手是Tornado)非同步網路框架來處理網路通訊,可以加快我們的下載速度,不用自己去實現非同步框架,並且包含了各種中介軟體介面,可以靈活的完成各種需求。
製作 Scrapy 爬蟲 一共需要4步:
- 新建專案 (scrapy startproject xxx):新建一個新的爬蟲專案
- 明確目標 (編寫items.py):明確你想要抓取的目標
- 製作爬蟲 (spiders/xxspider.py):製作爬蟲開始爬取網頁
- 儲存內容 (pipelines.py):設計管道儲存爬取內容
Scrapy的安裝介紹
Scrapy框架官方網址:http://doc.scrapy.org/en/latest
Scrapy中文維護站點:http://scrapy-chs.readthedocs.io/zh_CN/latest/index.html
Windows 安裝方式
- Python 2 / 3
- 升級pip版本:
pip install --upgrade pip
- 通過pip 安裝 Scrapy 框架
pip install Scrapy
一. 新建專案(scrapy startproject)
- 在開始爬取之前,必須建立一個新的Scrapy專案。進入自定義的專案目錄中,執行下列命令:
scrapy startproject HupuSpider
- 其中, HupuSpider為專案名稱(自定義),可以看到 將會建立一個 HupuSpider資料夾,目錄結構大致如下:
下面來簡單介紹一下各個主要檔案的作用:
scrapy.cfg :專案的配置檔案
mySpider/ :專案的Python模組,將會從這裡引用程式碼
mySpider/items.py :專案的目標檔案
mySpider/pipelines.py :專案的管道檔案
mySpider/settings.py :專案的設定檔案
mySpider/spiders/ :儲存爬蟲程式碼目錄
二、明確目標(mySpider/items.py)
我們打算抓取:https://voice.hupu.com/nba 網站裡的30支球隊新聞內容以及新聞配圖。
開啟mySpider目錄下的items.py
Item 定義結構化資料欄位,用來儲存爬取到的資料,有點像Python中的dict,但是提供了一些額外的保護減少錯誤。
可以通過建立一個 scrapy.Item 類, 並且定義型別為 scrapy.Field的類屬性來定義一個Item(可以理解成類似於ORM的對映關係)。
接下來,建立一個HupuSpiderItem類,和構建item模型(model)。
class HupuspiderItem(scrapy.Item): # 球隊名稱 teamname = scrapy.Field() # 球隊url teamurl = scrapy.Field() # 新聞標題 newstitle=scrapy.Field() # 新聞連結 newsurl=scrapy.Field() # 新聞內容 content=scrapy.Field() # 新聞配圖url imageurl=scrapy.Field()
三、製作爬蟲 (spiders/itcastSpider.py)
爬蟲功能要分兩步:
1. 爬資料
- 在當前目錄下輸入命令,將在
mySpider/spider
目錄下建立一個名為nba_news
的爬蟲,並指定爬取域的範圍:scrapy genspider nba_news "hupu.com"
- 開啟 mySpider/spider目錄裡的 nba_news.py,預設增加了下列程式碼:
import scrapy class NbaNewsSpider(scrapy.Spider): name = "nba_news" allowed_domains = ["hupu.com"] start_urls = ( 'https://www.hupu.com/', ) def parse(self, response): pass
其實也可以由我們自行建立itcast.py並編寫上面的程式碼,只不過使用命令可以免去編寫固定程式碼的麻煩
要建立一個Spider, 你必須用scrapy.Spider類建立一個子類,並確定了三個強制的屬性 和 一個方法。
name = ""
:這個爬蟲的識別名稱,必須是唯一的,在不同的爬蟲必須定義不同的名字。
allow_domains = []
是搜尋的域名範圍,也就是爬蟲的約束區域,規定爬蟲只爬取這個域名下的網頁,不存在的URL會被忽略。
start_urls = ()
:爬取的URL元祖/列表。爬蟲從這裡開始抓取資料,所以,第一次下載的資料將會從這些urls開始。其他子URL將會從這些起始URL中繼承性生成。
parse(self, response)
:解析的方法,每個初始URL完成下載後將被呼叫,呼叫的時候傳入從每一個URL傳回的Response物件來作為唯一引數,主要作用如下:
- 負責解析返回的網頁資料(response.body),提取結構化資料(生成item)
- 生成需要下一頁的URL請求。
將start_urls的值修改為需要爬取的第一個url
start_urls=['https://voice.hupu.com/nba/']
修改parse()方法
def parse(self, response): items=[] result=response.xpath('/html/body/div[2]/div/div[3]/div[2]/div[1]/ul') # 球隊隊名 team=result.xpath('.//li/a/text()').extract() allteam=team[:-1] team=result.xpath('.//li[last()]/div/a/text()').extract() allteam.extend(team) # 球隊url teamurl = result.xpath('.//li/a/@href').extract() allteamurl = teamurl[:-1] teamurl = result.xpath('.//li[last()]/div/a/@href').extract() allteamurl.extend(teamurl) # 爬取所有的球隊 for i in range(0,len(allteam)): item = HupuspiderItem() # 指定儲存目錄+球隊名字 teamFilename="./虎撲新聞/"+allteam[i] # 如果目錄不存在,則建立目錄 if (not os.path.exists(teamFilename)): os.makedirs(teamFilename) item['teamname']=allteam[i] item['teamurl']=allteamurl[i] items.append(item) #傳送每個球隊url的Request請求,得到Response連同包含meta資料 # 一同交給回撥函式 second_parse 方法處理 for item in items: for i in range(1,2): tempurl = item['teamurl'].replace('.html','') teamurl = tempurl + '-' + str(i) + '.html' yield scrapy.Request( url = teamurl, meta={'meta_1': item}, callback=self.second_parse) # 對每支球隊的url進行爬取 def second_parse(self,response): items=[] # 提取每次Response的meta資料 meta_1 = response.meta['meta_1'] # 提取每支球隊的所有新聞url allurl = response.xpath('//html/body/div[3]/div[1]/div/div[@class="list"]/div/div/span/a/@href').extract() # 提取每支球隊的所有新聞標題 alltitle = response.xpath('//html/body/div[3]/div[1]/div/div[@class="list"]/div/div/span/a/text()').extract() for i in range(0, len(alltitle)): item=HupuspiderItem() item['teamname'] = meta_1['teamname'] item['teamurl'] = meta_1['teamurl'] item['newstitle'] = alltitle[i] item['newsurl'] = allurl[i] items.append(item) # 指定儲存目錄+球隊名字+新聞標題資料夾 newsFilename = "./虎撲新聞/"+item['teamname']+'/' + alltitle[i] # 如果目錄不存在,則建立目錄 if (not os.path.exists(newsFilename)): os.makedirs(newsFilename) # 傳送每個新聞連結url的Request請求,得到Response後連同包含meta資料 # 一同交給回撥函式 detail_parse 方法處理 for item in items: yield scrapy.Request(url=item['newsurl'], meta={'meta_2': item}, callback=self.detail_parse) def detail_parse(self,response): item = response.meta['meta_2'] content = "" # 提取所有p標籤裡的文字內容 content_list = response.xpath('//html/body/div[4]/div[1]/div[2]/div/div[2]/p/text()').extract() # 提取配圖url imageurl = response.xpath('/html/body/div[4]/div[1]/div[2]/div/div[1]/img/@src').extract() # 將p標籤裡的文字內容合併到一起 for content_one in content_list: content += content_one content +="\n" item['content'] = content item['imageurl'] = imageurl # 將獲取的資料交給pipelines yield item
def parse(self, response): items=[] result=response.xpath('/html/body/div[2]/div/div[3]/div[2]/div[1]/ul') # 球隊隊名 team=result.xpath('.//li/a/text()').extract() allteam=team[:-1] team=result.xpath('.//li[last()]/div/a/text()').extract() allteam.extend(team) # 球隊url teamurl = result.xpath('.//li/a/@href').extract() allteamurl = teamurl[:-1] teamurl = result.xpath('.//li[last()]/div/a/@href').extract() allteamurl.extend(teamurl) # 爬取所有的球隊 for i in range(0,len(allteam)): item = HupuspiderItem() # 指定儲存目錄+球隊名字 teamFilename="./虎撲新聞/"+allteam[i] # 如果目錄不存在,則建立目錄 if (not os.path.exists(teamFilename)): os.makedirs(teamFilename) item['teamname']=allteam[i] item['teamurl']=allteamurl[i] items.append(item) #傳送每個球隊url的Request請求,得到Response連同包含meta資料 # 一同交給回撥函式 second_parse 方法處理 for item in items: for i in range(1,2): tempurl = item['teamurl'].replace('.html','') teamurl = tempurl + '-' + str(i) + '.html' yield scrapy.Request( url = teamurl, meta={'meta_1': item}, callback=self.second_parse) # 對每支球隊的url進行爬取 def second_parse(self,response): items=[] # 提取每次Response的meta資料 meta_1 = response.meta['meta_1'] # 提取每支球隊的所有新聞url allurl = response.xpath('//html/body/div[3]/div[1]/div/div[@class="list"]/div/div/span/a/@href').extract() # 提取每支球隊的所有新聞標題 alltitle = response.xpath('//html/body/div[3]/div[1]/div/div[@class="list"]/div/div/span/a/text()').extract() for i in range(0, len(alltitle)): item=HupuspiderItem() item['teamname'] = meta_1['teamname'] item['teamurl'] = meta_1['teamurl'] item['newstitle'] = alltitle[i] item['newsurl'] = allurl[i] items.append(item) # 指定儲存目錄+球隊名字+新聞標題資料夾 newsFilename = "./虎撲新聞/"+item['teamname']+'/' + alltitle[i] # 如果目錄不存在,則建立目錄 if (not os.path.exists(newsFilename)): os.makedirs(newsFilename) # 傳送每個新聞連結url的Request請求,得到Response後連同包含meta資料 # 一同交給回撥函式 detail_parse 方法處理 for item in items: yield scrapy.Request(url=item['newsurl'], meta={'meta_2': item}, callback=self.detail_parse) def detail_parse(self,response): item = response.meta['meta_2'] content = "" # 提取所有p標籤裡的文字內容 content_list = response.xpath('//html/body/div[4]/div[1]/div[2]/div/div[2]/p/text()').extract() # 提取配圖url imageurl = response.xpath('/html/body/div[4]/div[1]/div[2]/div/div[1]/img/@src').extract() # 將p標籤裡的文字內容合併到一起 for content_one in content_list: content += content_one content +="\n" item['content'] = content item['imageurl'] = imageurl # 將獲取的資料交給pipelines yield item2. 取資料
這個專案運用的是XPath提取資料。
result=response.xpath('/html/body/div[2]/div/div[3]/div[2]/div[1]/ul') # 球隊隊名 team=result.xpath('.//li/a/text()').extract() allteam=team[:-1] team=result.xpath('.//li[last()]/div/a/text()').extract() allteam.extend(team) # 球隊url teamurl = result.xpath('.//li/a/@href').extract() allteamurl = teamurl[:-1] teamurl = result.xpath('.//li[last()]/div/a/@href').extract() allteamurl.extend(teamurl)
result=response.xpath('/html/body/div[2]/div/div[3]/div[2]/div[1]/ul') # 球隊隊名 team=result.xpath('.//li/a/text()').extract() allteam=team[:-1] team=result.xpath('.//li[last()]/div/a/text()').extract() allteam.extend(team) # 球隊url teamurl = result.xpath('.//li/a/@href').extract() allteamurl = teamurl[:-1] teamurl = result.xpath('.//li[last()]/div/a/@href').extract() allteamurl.extend(teamurl)
# 提取每支球隊的所有新聞url allurl = response.xpath('//html/body/div[3]/div[1]/div/div[@class="list"]/div/div/span/a/@href').extract() # 提取每支球隊的所有新聞標題 alltitle = response.xpath('//html/body/div[3]/div[1]/div/div[@class="list"]/div/div/span/a/text()').extract()
# 提取所有p標籤裡的文字內容 content_list = response.xpath('//html/body/div[4]/div[1]/div[2]/div/div[2]/p/text()').extract() # 提取配圖url imageurl = response.xpath('/html/body/div[4]/div[1]/div[2]/div/div[1]/img/@src').extract()
- 我們之前在mySpider/items.py 裡定義了一個HupuspiderItem類。 這裡引入進來
from HupuSpider.items import HupuspiderItem
- 然後將我們得到的資料封裝到一個 HupuspiderItem物件中,可以儲存每條新聞的屬性:
item = HupuspiderItem()
設定setting.py(配置引數):
BOT_NAME = 'HupuSpider' SPIDER_MODULES = ['HupuSpider.spiders'] NEWSPIDER_MODULE = 'HupuSpider.spiders' # 函式的執行順序,序號越小,優先順序越高 ITEM_PIPELINES = { 'HupuSpider.pipelines.HupuspiderPipeline': 1, 'HupuSpider.pipelines.HupuImagesPipeline':2, } LOG_LEVEL='DEBUG' ROBOTSTXT_OBEY = True # Images 的存放位置,之後會在pipelines.py裡呼叫 IMAGES_STORE='E:/學習Python/HupuSpider/HupuSpider/虎撲新聞'
Item Pipeline
當Item在Spider中被收集之後,它將會被傳遞到Item Pipeline,這些Item Pipeline元件按定義的順序處理Item。
每個Item Pipeline都是實現了簡單方法的Python類,比如決定此Item是丟棄而儲存。以下是item pipeline的一些典型應用:
- 驗證爬取的資料(檢查item包含某些欄位,比如說name欄位)
- 查重(並丟棄)
- 將爬取結果儲存到檔案或者資料庫中
編寫pipeline.py
from scrapy.pipelines.images import ImagesPipeline from scrapy.utils.project import get_project_settings import scrapy import os class HupuspiderPipeline(object): def process_item(self, item, spider): # 新聞標題作為資料夾名字 filename = item['newstitle'] filename += ".txt" # 每條新聞放到對應的球隊資料夾中 savepath='虎撲新聞'+'/'+item['teamname']+'/'+ item['newstitle'] +'/'+filename fp = open(savepath, 'w',encoding='utf-8') fp.write(item['content']) fp.close() return item class HupuImagesPipeline(ImagesPipeline): IMAGES_STORE = get_project_settings().get("IMAGES_STORE") def get_media_requests(self, item, info): image_url = item["imageurl"] yield scrapy.Request(image_url[0]) def item_completed(self, results, item, info): # 固定寫法,獲取圖片路徑,同時判斷這個路徑是否正確,如果正確, # 就放到 image_path裡,ImagesPipeline原始碼剖析可見 image_path = [x["path"] for ok, x in results if ok] # 每張新聞配圖放到對應的球隊資料夾中 os.rename(self.IMAGES_STORE + "/" + image_path[0], self.IMAGES_STORE + "/" + item["teamname"] + "/" + item["newstitle"] + "/" + item[ "newstitle"] + ".jpg") return item #get_media_requests的作用就是為每一個圖片連結生成一個Request物件, # 這個方法的輸出將作為item_completed的輸入中的results, # results是一個元組,每個元組包括(success, imageinfoorfailure)。 # 如果success=true,imageinfoor_failure是一個字典, # 包括url/path/checksum三個key。
在專案根目錄下新建main.py檔案,用於除錯
from scrapy import cmdline cmdline.execute('scrapy crawl douyu'.split())
執行程式後會自動生成一個"虎撲新聞"資料夾
雙擊開啟"虎撲新聞"資料夾,對nba 30支球隊進行了分類
雙擊開啟"雷霆"新聞資料夾
雙擊開啟第一條新聞"被問及若加盟湖人會不會興奮?喬治:下一個問題",資料夾裡面是新聞配圖和新聞內容文字
開啟文字就是我們所要的新聞內容
這是從虎撲網頁擷取的相同新聞配圖和新聞內容,我們已經完成了需求