python scrapy 入门爬虫 「什么值得买」关键字搜索

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/alcoholdi/article/details/79797493

安装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,从某个具体的元素中取值。

一个例子如下

扫描二维码关注公众号,回复: 3027849 查看本文章
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


猜你喜欢

转载自blog.csdn.net/alcoholdi/article/details/79797493
今日推荐