爬蟲寫法進階:普通函數--->函數類--->Scrapy框架


本文轉載自以下網站: 從 Class 類到 Scrapy https://www.makcyun.top/web_scraping_withpython12.html

 

普通函數爬蟲: https://www.cnblogs.com/sanduzxcvbnm/p/10271493.html  

函數類爬蟲:https://www.makcyun.top/web_scraping_withpython7.html

Scrapy框架爬蟲: https://www.cnblogs.com/sanduzxcvbnm/p/10276729.html

 

對於 Python 初學者來說,習慣使用函數寫代碼后,開始學 Scrapy 會感到很復雜,不知如何下手寫代碼,本文通過實際案例,對比普通函數(類)和 Scrapy 中代碼的寫法,助你快速入門 Scrapy。

摘要:通過實際爬蟲案例,分別用普通函數(類)和 Scrapy 進行實現,通過代碼,助你快速入門 Scrapy。

上一篇文章,我們通過 3 個實際爬蟲案例,分別用函數(def)和 類(Class) 兩種方法進行了實現,相信能夠幫助你加深對類(Class)概念和用法的理解。在該文的第 3 個例子中,我們從類的寫法延伸到了 Pyspider 中類代碼的寫法,本文進一步補充,通過實際爬蟲案例分別用普通類的寫法和 Scrapy 中類代碼的寫法進行實現。

從函數 def 到類 Class

Scrapy 爬蟲框架非常強大,但是初學起來會覺得有點復雜,因為完整的一段代碼需要拆分放在不同的模塊下,比如寫一個爬蟲,原先我們只需要用函數或者類從頭寫到尾即可,一目了然,但是在 Scrapy 中則不同,我們首先要在 items.py 中定義爬取的字段內容,在主程序模塊中編寫爬蟲主程序,在 pipeline.py模塊中實現數據處理、存儲,在 middlewares.py 模塊中定義代理 IP、UA 等。

總之代碼的寫法會發生一些變化,我在沒適應用 Scrapy 之前,習慣在 Sublime 中完整地用函數實現一遍,然后再遷移到 Scrapy 框架中,雖然慢,但是寫多幾次后就適應了Scrapy 的寫法,這比一上來就直接在 Scrapy 中寫過渡地要順利一些。

好,下面我們就以之前一篇爬取酷安 App 的文章為例進行說明,這篇文章用了 Scrapy 來實現,下面再用普通的函數寫法實現一遍,並對關鍵的地方進行一下對比。

Scrapy 爬取並分析酷安 6000 款 App,找到良心佳軟

▌爬取思路分析

在上面這篇文章里,我面已經對 目標網站 進行了分析,這里簡單回顧一下,便於把握后續的抓取思路。

首先,網頁請求是 GET 形式,URL 只有一個頁數遞增參數,構造翻頁非常簡單。每頁顯示了 10 條 App 信息,通過點擊尾頁,發現一共有 610 頁,也就是說一共有 6100 款左右的 App 。

接下來,我們需要進入每一個 App 的主頁,抓取 App 相關字段信息,確定了 8 個關鍵字段,分別是:App 名稱、下載量、評分、評分人數、評論數、關注人數、體積、App 分類標簽。

然后,打開網頁后台,利用正則表達式、CSS分別提取每個字段的信息即可。

如果你還不熟悉正則、CSS、Xpath 這幾種網頁內容提取方法,可以參考我早先總結的這篇文章:

四種方法爬取貓眼 TOP100 電影

通過上述分析,就可以確定爬取思路了:首先可以通過兩種方式構造分頁循環,一種是利用 for 循環直接構造 610 頁 URL 鏈接,另外一種是獲取下一頁的節點,不斷遞增直到最后一頁。第一種方式簡單但只適合總頁數確定的形式,第二種方式稍微復雜一點,但不管知不知道總頁數都可以循環。

接着,每頁抓取 10 款 App URL,進入 App 詳情頁后,利用 CSS 語法抓取每個 App 的 8 個字段信息,最后保存到 MongoDB中,結果形式如下:

下面我們就來實操對比一下。

▌獲取網頁 Response

首先,遍歷每頁的 URL 請求獲得響應 Response,提取每款 App 主頁的 URL 請求,以便下一步解析提取字段內容。

def 寫法:

兩次 for 循環,提取所有的 URL 鏈接,供下一步解析內容。

headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
}
def get_page(page):
url = 'https://www.coolapk.com/apk?p=%s' %page
response = requests.get(url,headers=headers).text
content = pq(response)('.app_left_list>a').items()
urls = []
for item in content:
url = urljoin('https://www.coolapk.com',item.attr('href'))
urls.append(url)
return urls

if __name__ == '__main__':
for page in range(1, 610):
get_page(page)

Scrapy 寫法:

class KuspiderSpider(scrapy.Spider):
name = 'kuspider'
allowed_domains = ['www.coolapk.com']
start_urls = ['https://www.coolapk.com/apk/']
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
}
def start_requests(self):
pages = []
for page in range(1,610):
url = 'https://www.coolapk.com/apk/?page=%s' % page
page = scrapy.Request(
url, callback=self.parse, headers=self.headers)
pages.append(page)
return pages

def parse(self, response):
contents = response.css('.app_left_list>a')
for content in contents:
url = content.css('::attr("href")').extract_first()
url = response.urljoin(url)
yield scrapy.Request(url, callback=self.parse_url)

這里有幾點不同的地方,簡單進行說明:

  • 循環構造方式不同

    普通函數用兩個 for 循環就可以,Scrapy 中是構造最外層的循環,實現方法是先構造一個空列表,存放 page,URL 構造好之后通過 scrapy.Request () 方法進行請求,獲得響應 response ,傳遞給 callback 參數指定的 parse() 方法,再進一步進行第二個 for 循環。

  • 內容提取形式不同

    以 CSS 語法提取為例,普通函數和 Scrapy 中內容提取的方法稍有不同, 下面以提取提取單個節點文本、提取屬性、提取多個節點,這三種最為常見的提取形式為例,將普通函數和 Scrapy 的寫法進行對比:


  • #提取單個節點文本
    name = item('.list_app_title').text()
    name = item.css('.detail_app_title::text').extract_first()
    #提取屬性
    url = item('.app_left_list>a').attr('href')
    url = item.css('::attr("href")').extract_first()
    #提取多個節點
    content = pq(response)('.app_left_list>a').items()
    contents = response.css('.app_left_list>a')

這里順便再說一下 Scrapy 遍歷分頁的第二種方式。

如果不通過構造 for 循環的方式遍歷,可以先請求第一頁獲得 response 進行解析,然后再獲取下一頁 url 重復調用解析方法,直到解析完最后一頁為止,這種方法 start_requests 構造就很簡單,直接傳遞 url 到下一個 parse() 方法即可。

def start_requests(self):
url = 'https://www.coolapk.com/apk/?page=1'
yield scrapy.Request(
url, callback=self.parse, headers=self.headers)
pages.append(page)
return pages
def parse(self, response):
contents = response.css('.app_left_list>a')
for content in contents:
url = content.css('::attr("href")').extract_first()
url = response.urljoin(url)
yield scrapy.Request(url, callback=self.parse_url)

next_page = response.css('.pagination li:nth-child(8) a::attr("href")').extract_first()
url = response.urljoin(next_page)
yield scrapy.Request(url,callback=self.parse )

▌解析網頁提取字段

接下來,我們就要提取App 名稱、下載量、評分這些字段信息了。

def 寫法:

def parse_content(urls):
lst = []
for url in urls:
response = requests.get(url,headers=headers).text
doc = pq(response) # pyquery解析
name = doc('.detail_app_title').remove('span').text()
# 若不想要子節點文本則先去除掉
results = get_comment(doc)
tags = get_tags(doc)
score = doc('.rank_num').text()
# 評論數
num_score = doc('.apk_rank_p1').text()
num_score = re.search('共(.*?)個評分',num_score).group(1)
data ={
'name':name,
'volume':results[0],
'download':results[1],
'follow':results[2],
'comment':results[3],
'tags':str(tags),
'score':score,
'num_score':num_score
}
lst.append(data)
data = pd.DataFrame(lst)
return data

這里,值得注意一點:

pyquery 提取文本的時候,默認會提取節點內所有的文本內容,如果你只想要其中某個節點的,那么最好先刪除掉不需要的節點,再提取文本。

比如這里,我們在提取 app 名稱的時候,如果直接用:

name = doc('.detail_app_title')text()

提取出來的則是「酷安 8.8.3」,如果只想要「酷安」,不想要下面的版本信息:8.8.3,需要刪除子節點 span 后再提取:

name = doc('.detail_app_title').remove('span').text()

Scrapy 寫法:

獲取字段信息,我們需要現在 settings.py 中設置,然后才能提取。

class KuanItem(scrapy.Item):
# define the fields for your item here like:
name = scrapy.Field()
volume = scrapy.Field()
download = scrapy.Field()
follow = scrapy.Field()
comment = scrapy.Field()
tags = scrapy.Field()
score = scrapy.Field()
num_score = scrapy.Field()

回到主程序中,通過 item = Kuan2Item() 來調用上面定義的字段信息。

def parse(self, response):
contents = response.css('.app_left_list>a')
for content in contents:
url = content.css('::attr("href")').extract_first()
url = response.urljoin(url)
yield scrapy.Request(url, callback=self.parse_url)

def parse_url(self, response):
item = Kuan2Item()
item['name'] = response.css('.detail_app_title::text').extract_first()
results = self.get_comment(response)
item['volume'] = results[0]
item['download'] = results[1]
item['follow'] = results[2]
item['comment'] = results[3]
item['tags'] = self.get_tags(response)
item['score'] = response.css('.rank_num::text').extract_first()
num_score = response.css('.apk_rank_p1::text').extract_first()
item['num_score'] = re.search('共(.*?)個評分', num_score).group(1)
yield item

▌存儲到 MongoDB

提取完信息以后,我們便可以選擇將數據存儲到 MongoDB 中。

通過上面的方法,我們提取出了字段內容 data,然后轉換為了 DataFrame,DataFrame 存儲到 MongoDB 非常簡單,幾行代碼就能搞定。

def 寫法:

client = pymongo.MongoClient('localhost',27017)
db = client.KuAn
mongo_collection = db.kuan
def save_file(data):
content = json.loads(data.T.to_json()).values()
if mongo_collection.insert_many(content):
print('存儲到 mongondb 成功')

這里用了 inset_many () 方法來插入數據,但其實不太建議,因為一旦出現爬蟲中斷,我們再接着爬的時候,它會插入重復數據,雖然我們可以再后續處理時去除重復數據,但有更好的方法,那就是用 update_one() 方法,該方法能夠保證直插入新數據,重復數據不插入,下面我們在 Scrapy 中使用:

Scrapy 寫法:

class MongoPipeline(object):
def __init__(self,mongo_url,mongo_db):
self.mongo_url = mongo_url
self.mongo_db = mongo_db

@classmethod
def from_crawler(cls,crawler):
return cls(
mongo_url = crawler.settings.get('MONGO_URL'),
mongo_db = crawler.settings.get('MONGO_DB')
)

def open_spider(self,spider):
self.client = pymongo.MongoClient(self.mongo_url)
self.db = self.client[self.mongo_db]

def process_item(self,item,spider):
name = item.__class__.__name__
# update_one 方法可以不插入重復內容
self.db[name].update_one(item, {'$set': item}, upsert=True)
return item

def close_spider(self,spider):
self.client.close()

簡單說明幾點:

from crawler() 是一個類方法,用 @class method 標識,這個方法的作用主要是用來獲取我們在 settings.py 中設置的這幾項參數:

MONGO_URL = 'localhost'
MONGO_DB = 'KuAn'
ITEM_PIPELINES = {
'kuan.pipelines.MongoPipeline': 300,
}

open_spider() 方法主要進行一些初始化操作 ,在 Spider 開啟時,這個方法就會被調用 。

process_item() 方法是最重要的方法,實現插入數據到 MongoDB 中。

Scrapy 字段提取后,通過 yield 返回的是生成器,內容是單個字典信息,此時,我們可以下面這句代碼,實現只插入新數據,忽略重復數據。

self.db[name].update_one(item, {'$set': item}, upsert=True)

以上,我們從獲取網頁 Response、解析內容、MongoDB 存儲三個方面,對比了普通函數和 Scrapy 代碼的寫法,這三部分內容是多數爬蟲的主要部分。當然,還有其他的內容比如:下載圖片、反爬措施等,我們留在后續的 Scrapy 文章中繼續介紹。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM