安装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())
项目连接:https://github.com/yaodiwei/smzdm