python scrapy 入門爬蟲 「什麼值得買」關鍵字搜尋
安裝scrapy框架
pip install Scrapy
建立一個scrapy工程,名字為smzdm
scrapy startproject smzdm
建立包含下列內容的 smzdm 目錄:
smzdm/
scrapy.cfg
smzdm/
__init__.py
items.py
pipelines.py
settings.py
spiders/
__init__.py
...
這些檔案分別是:
scrapy.cfg: 專案的配置檔案
smzdm/: 該專案的python模組。之後您將在此加入程式碼。
smzdm/items.py: 專案中的item檔案.
smzdm/pipelines.py: 專案中的pipelines檔案.
smzdm/settings.py: 專案的設定檔案.
smzdm/spiders/: 放置spider程式碼的目錄.
進入工程目錄,使用以下命令建立一個爬蟲(Spider)檔案
scrapy genspider concrete_search search.smzdm.com
將會spiders目錄下,生成name='concrete_search' start_urls='search.smzdm.com' 的 concrete_search.py檔案
import scrapy class ConcreteSearchSpider(scrapy.Spider): name = 'concrete_search' allowed_domains = ['search.smzdm.com/'] start_urls = ['http://search.smzdm.com/'] def parse(self, response): pass
檔案建立了一個ConcreteSearchSpider繼承 scrapy.Spider 類, 且定義以下三個屬性:
- name: 用於區別Spider。 該名字必須是唯一的,您不可以為不同的Spider設定相同的名字。
- start_urls: 包含了Spider在啟動時進行爬取的url列表。 因此,第一個被獲取到的頁面將是其中之一。 後續的URL則從初始的URL獲取到的資料中提取。
- parse() 是spider的一個方法。 被呼叫時,每個初始URL完成下載後生成的 Response 物件將會作為唯一的引數傳遞給該函式。 該方法負責解析返回的資料(response data),提取資料(生成item)以及生成需要進一步處理的URL的 Request 物件。
在items.py檔案中,定義出需要採集的資料實體
# -*- coding: utf-8 -*-
import scrapy
class SmzdmItem(scrapy.Item):
id = scrapy.Field() #id
title = scrapy.Field() #標題
price = scrapy.Field() #價格
desc = scrapy.Field() #描述
zhi_yes = scrapy.Field() #點「值」的數量
zhi_no = scrapy.Field() #點「不值」的數量
praise = scrapy.Field() #文章「贊」的數量
start = scrapy.Field() #「收藏」的數量
comment = scrapy.Field() #「評論」的數量
time = scrapy.Field() #釋出時間
channel = scrapy.Field() #購買渠道
detail_url = scrapy.Field() #詳情頁面url
url = scrapy.Field() #商品連結
img = scrapy.Field() #商品圖片
def __str__(self):
return 'SmzdmItem (%s) (%s) (值:%s) (%s)' % (self['title'], self['price'], self['start'], self['detail_url'])
__repr__ = __str__
這裡我定義了一個SmzdmItem,繼承了scrapy.Item。並且重寫了__str__和__repr__,方便列印時能比較方便看到資訊。
解析網頁
在spider檔案中的parse方法裡,一般使用xpath、css來解析html,從某個具體的元素中取值。
一個例子如下
def parse(self, response):
sites = response.css('#site-list-content > div.site-item > div.title-and-desc')
items = []
for site in sites:
item = Website()
item['name'] = site.css(
'a > div.site-title::text').extract_first().strip()
item['url'] = site.xpath(
'a/@href').extract_first().strip()
item['description'] = site.css(
'div.site-descr::text').extract_first().strip()
items.append(item)
在chrome裡可以這樣獲取到xpath
關閉『遵循robot協議』
大部分網站都在robot協議中(比如https://www.smzdm.com/robots.txt)禁止了內容的爬去
scrapy預設遵守了網站的這個robot協議。
所以為了成功爬取內容,需要在settings.py中,把ROBOTSTXT_OBEY置為False
ROBOTSTXT_OBEY = False
使用pipelines.py進行篩選
用於去重或者篩選item
from scrapy.exceptions import DropItem
class SmzdmPipeline(object):
# 3.過濾條件
# 3.1表示商品的『值』數量必須 >= zhi_yes_limit
zhi_yes_limit = -1
# 3.2表示商品的『不值』數量必須 <= zhi_no_limit
zhi_no_limit = -1
# 3.3表示商品的『值』除以『不值』的比率必須 >= zhi_no_limit
zhi_ratio_limit = -1
# 3.4表示商品的『收藏』數量必須 >= start_limit
start_limit = -1
# 3.5表示商品的『評論』數量必須 >= comment_limit
comment_limit = -1
# 3.6需要排除的關鍵字
exclude = []
def __init__(self):
self.ids_seen = set()
def process_item(self, item, spider):
if item['id'] in self.ids_seen:
raise DropItem("Duplicate item found: %s" % item)
else:
self.ids_seen.add(item['id'])
if SmzdmPipeline.zhi_yes_limit > -1 and item['zhi_yes'] < SmzdmPipeline.zhi_yes_limit:
raise DropItem("zhi_yes_limit: %d, target: %s" % (SmzdmPipeline.zhi_yes_limit, str(item)))
if SmzdmPipeline.zhi_no_limit > -1 and item['zhi_no'] > SmzdmPipeline.zhi_no_limit:
raise DropItem("zhi_no_limit: %d, target: %s" % (SmzdmPipeline.zhi_no_limit, item))
if SmzdmPipeline.zhi_ratio_limit > -1 and (item['zhi_yes'] / item['zhi_no']) > SmzdmPipeline.zhi_ratio_limit:
raise DropItem("zhi_ratio_limit: %d, target: %s" % (SmzdmPipeline.zhi_ratio_limit, item))
if SmzdmPipeline.start_limit > -1 and item['zhi_start'] < SmzdmPipeline.zhi_start_limit:
raise DropItem("zhi_start_limit: %d, target: %s" % (SmzdmPipeline.zhi_start_limit, item))
if SmzdmPipeline.comment_limit > -1 and item['zhi_commen'] <= SmzdmPipeline.comment_limit:
raise DropItem("zhi_comment_limit: %d, target: %s" % (SmzdmPipeline.zhi_comment_limit, item))
if len(SmzdmPipeline.exclude) > 0 and SmzdmPipeline.containsKeyword(item['title']):
raise DropItem("exclude: %s, target: %s" % (str(SmzdmPipeline.exclude), item))
print("符合條件:" + str(item))
return item
@classmethod
def containsKeyword(cls, title):
for keyword in cls.exclude:
if keyword in title:
return True
return False
這裡我寫了一個,裡面有去重的程式碼,還有根據點值人數,收藏人數,點值/點不值比率篩選的邏輯
寫完後需要到settings.py開啟這個功能
ITEM_PIPELINES = {
'smzdm.pipelines.SmzdmPipeline': 300,
}
分配給每個類的整型值,確定了他們執行的順序,item按數字從低到高的順序,通過pipeline,通常將這些數字定義在0-1000範圍內。
啟動爬蟲
進入專案的根目錄,執行下列命令啟動爬蟲:
scrapy crawl concrete_search
為了方便,我在 concrete_search.py 加入了以下程式碼
if __name__ == "__main__":
name = 'concrete_search'
cmd = 'scrapy crawl {0}'.format(name)
cmdline.execute(cmd.split())
這樣直接執行該檔案就可以啟動爬蟲了。
============================================================================
以下是我寫的爬蟲的程式碼,篩選條件在__init__方法中可以修改
# -*- coding: utf-8 -*-
from urllib import parse
import scrapy
from scrapy import cmdline
from smzdm.items import SmzdmItem
from smzdm.pipelines import SmzdmPipeline
class ConcreteSearchSpider(scrapy.Spider):
# 爬蟲的名字
name = 'concrete_search'
# value:SmzdmItem
SmzdmItemList = []
def __init__(self):
# 1.搜尋的商品名稱
feed = ['奶粉']
# 2.需要搜尋的頁數,一頁為20個商品
pages = 5
# 3.過濾條件
# 3.1表示商品的『值』數量必須 >= zhi_yes_limit
zhi_yes_limit = 5
# 3.2表示商品的『不值』數量必須 <= zhi_no_limit
zhi_no_limit = -1
# 3.3表示商品的『值』除以『不值』的比率必須 >= zhi_no_limit
zhi_ratio_limit = -1
# 3.4表示商品的『收藏』數量必須 >= start_limit
start_limit = -1
# 3.5表示商品的『評論』數量必須 >= comment_limit
comment_limit = -1
# 3.6需要排除的關鍵字
exclude = ['嬰兒', '幼兒', '寶寶']
SmzdmPipeline.zhi_yes_limit = zhi_yes_limit
SmzdmPipeline.zhi_no_limit = zhi_no_limit
SmzdmPipeline.zhi_ratio_limit = zhi_ratio_limit
SmzdmPipeline.start_limit = start_limit
SmzdmPipeline.comment_limit = comment_limit
SmzdmPipeline.exclude = exclude
for index, f in enumerate(feed):
feed[index] = parse.quote(f)
self.start_urls = ['http://search.smzdm.com/?c=home&s=%s&p=%d&v=a' % (y, x) for x in range(1, pages + 1) for y
in feed]
def parse(self, response):
selector_feed_main_list = response.selector.xpath('//*[@id="feed-main-list"]')[0]
selector_list_feed_main_list = selector_feed_main_list.xpath('./li')
for selector in selector_list_feed_main_list:
selector_item = selector.xpath('./div/div[2]')
# 過濾文章
if len(selector_item.xpath('./div[2]/div[2]/div/div/a')) == 0 or len(
selector_item.xpath('./h5/a[2]/div/text()')) == 0:
print(
"發現一篇資訊 : 獲取%s個贊,名字如下" % selector_item.xpath('./div[2]/div[1]/span[1]/span[1]/text()')[0].extract())
print(selector_item.xpath('./h5/a[1]/text()')[0].extract())
continue
item = SmzdmItem()
item['id'] = int(selector_item.xpath('./div[2]/div[1]/span[1]/span[1]/@data-article')[0].extract())
item['title'] = selector_item.xpath('./h5/a[1]/text()')[0].extract().strip()
if len(selector_item.xpath('./h5/a[2]/div/text()')) != 0:
item['price'] = selector_item.xpath('./h5/a[2]/div/text()')[0].extract()
desc_text_count = len(selector_item.xpath('./div[1]/text()').extract())
if desc_text_count == 1 or selector_item.xpath('./div[1]/text()').extract()[0].strip() != '':
item['desc'] = selector_item.xpath('./div[1]/text()')[0].extract().strip()
elif desc_text_count >= 2 and selector_item.xpath('./div[1]/text()').extract()[1].strip() != '':
item['desc'] = selector_item.xpath('./div[1]/text()')[1].extract().strip()
if len(selector_item.xpath('./div[2]/div[1]/span[1]/span[1]/span[1]/span/text()')) != 0:
item['zhi_yes'] = int(selector_item.xpath('./div[2]/div[1]/span[1]/span[1]/span[1]/span/text()')[
0].extract())
item['zhi_no'] = int(selector_item.xpath('./div[2]/div[1]/span[1]/span[2]/span[1]/span/text()')[0].extract())
item['start'] = int(selector_item.xpath('./div[2]/div[1]/span[2]/span/text()')[0].extract())
# 待優化 item['comment'] = selector.xpath('./div[2]/div[1]/a/text()')[0].extract()
item['comment'] = int(selector_item.xpath('./div[2]/div[1]/a/@title')[0].extract().split(' ')[1])
item['time'] = selector_item.xpath('./div[2]/div[2]/span/text()')[0].extract().strip()
item['channel'] = selector_item.xpath('./div[2]/div[2]/span/span/text()')[0].extract().strip()
item['detail_url'] = selector.xpath('./div/div[1]/a/@href')[0].extract().strip()
item['url'] = selector_item.xpath('./div[2]/div[2]/div/div/a/@href')[0].extract()
item['img'] = selector.xpath('./div/div[1]/a/img/@src')[0].extract()
# print(item)
ConcreteSearchSpider.SmzdmItemList.append(item)
yield item
# print(ConcreteSearchSpider.SmzdmItemList)
if __name__ == "__main__":
name = 'concrete_search'
cmd = 'scrapy crawl {0}'.format(name)
cmdline.execute(cmd.split())