基于scrapy和mysql实现简书全站爬虫

2019-07-14  本文已影响0人  stay丶gold

说明:本文仅供初学者学习交流;请勿用作其他用途
参考:https://www.cnblogs.com/wupeiqi/p/6229292.html

Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 其可以应用在数据挖掘,信息处理或存储历史数据等一系列的程序中。
其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的, 也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试。

Scrapy 使用了 Twisted异步网络库来处理网络通讯。整体架构大致如下

image

Scrapy主要包括了以下组件:

Scrapy运行流程大概如下:

  1. 引擎从调度器中取出一个链接(URL)用于接下来的抓取
  2. 引擎把URL封装成一个请求(Request)传给下载器
  3. 下载器把资源下载下来,并封装成应答包(Response)
  4. 爬虫解析Response
  5. 解析出实体(Item),则交给实体管道进行进一步的处理
  6. 解析出的是链接(URL),则把URL交给调度器等待抓取

以下实现的一个简单的简书全站爬虫,并存储到mysql数据库

# -*- coding: utf-8 -*-
"""
  js.py
"""
import scrapy
import json
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..items import Article, Author


class JsSpider(CrawlSpider):
    name = 'js'
    allowed_domains = ['jianshu.com']
    start_urls = ['http://jianshu.com/']

    rules = (
        Rule(LinkExtractor(allow=r'.*/p/[0-9a-z]{12}.*'), callback='parse_detail', follow=True),
    )

    def parse_detail(self, response):
        title = response.xpath('//h1[@class="title"]/text()').get()
        content = response.xpath('//div[@class="show-content"]').get()
        origin_url = response.url.split("?")[0]
        article_id = origin_url.split("/")[-1]
        author1 = response.xpath('//span[@class="name"]/a/text()').get()
        avatar = response.xpath('//a[@class="avatar"]/img/@src').get()
        page_date = response.xpath('//script[@data-name="page-data"]/text()').get()
        data = json.loads(page_date)
        pub_time = response.xpath('//span[@class="publish-time"]/text()').get().split()[0]
        word_num = data.get("public_wordage")
        read_num = data.get("views_count")
        comment_num = data.get("comments_count")
        likes_num = data.get("likes_count")
        rewards_num = data.get("total_rewards_count")

        article = Article(title=title,
                          content=content,
                          origin_url=origin_url,
                          article_id=article_id,
                          author=author1,
                          avatar=avatar)
        yield article

"""
items.py
"""


import scrapy
from scrapy import Field

class Article(scrapy.Item):
    title = Field()
    content = Field()
    article_id = Field()
    author = Field()
    origin_url = Field()
    avatar = Field()


class Author(scrapy.Item):
    nickname = Field()
    total_likes_count = Field()
    total_wordage = Field()
    followers_count = Field()
# -*- coding: utf-8 -*-

"""
DownloaderMiddlewar:下载中间件
SpiderMiddleware:爬虫中间件
engine-->调度器--->request--->下载中间件--->response--->spider
-->爬虫中间件-->(pipeline持久化或者engine)

内置下载中间件
    {
        'scrapy.contrib.downloadermiddleware.robotstxt.RobotsTxtMiddleware': 100,
        'scrapy.contrib.downloadermiddleware.httpauth.HttpAuthMiddleware': 300,
        'scrapy.contrib.downloadermiddleware.downloadtimeout.DownloadTimeoutMiddleware': 350,
        'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': 400,
        'scrapy.contrib.downloadermiddleware.retry.RetryMiddleware': 500,
        'scrapy.contrib.downloadermiddleware.defaultheaders.DefaultHeadersMiddleware': 550,
        'scrapy.contrib.downloadermiddleware.redirect.MetaRefreshMiddleware': 580,
        'scrapy.contrib.downloadermiddleware.httpcompression.HttpCompressionMiddleware': 590,
        'scrapy.contrib.downloadermiddleware.redirect.RedirectMiddleware': 600,
        'scrapy.contrib.downloadermiddleware.cookies.CookiesMiddleware': 700,
        'scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware': 750,
        'scrapy.contrib.downloadermiddleware.chunked.ChunkedTransferMiddleware': 830,
        'scrapy.contrib.downloadermiddleware.stats.DownloaderStats': 850,
        'scrapy.contrib.downloadermiddleware.httpcache.HttpCacheMiddleware': 900,
    }

内置爬虫中间件:
    'scrapy.contrib.spidermiddleware.httperror.HttpErrorMiddleware': 50,
    'scrapy.contrib.spidermiddleware.offsite.OffsiteMiddleware': 500,
    'scrapy.contrib.spidermiddleware.referer.RefererMiddleware': 700,
    'scrapy.contrib.spidermiddleware.urllength.UrlLengthMiddleware': 800,
    'scrapy.contrib.spidermiddleware.depth.DepthMiddleware': 900,

"""


from scrapy import signals


class JianshuSpiderSpiderMiddleware(object):
    # Not all methods need to be defined. If a method is not defined,
    # scrapy acts as if the spider middleware does not modify the
    # passed objects.

    @classmethod
    def from_crawler(cls, crawler):
        # This method is used by Scrapy to create your spiders.
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    def process_spider_input(self, response, spider):
        # Called for each response that goes through the spider
        # middleware and into the spider.
        #下载完成,response交给spider
        # Should return None or raise an exception.
        return None

    def process_spider_output(self, response, result, spider):
        # Called with the results returned from the Spider, after
        # it has processed the response.
        #spider执行完成以后执行
        # Must return an iterable of Request, dict or Item objects.
        for i in result:
            yield i

    def process_spider_exception(self, response, exception, spider):
        # Called when a spider or process_spider_input() method
        # (from other spider middleware) raises an exception.

        # Should return either None or an iterable of Response, dict
        # or Item objects.
        pass

    def process_start_requests(self, start_requests, spider):
        # Called with the start requests of the spider, and works
        # similarly to the process_spider_output() method, except
        # that it doesn’t have a response associated.
        #爬虫启动时调用
        # Must return only requests (not items).
        for r in start_requests:
            yield r

    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)


class JianshuSpiderDownloaderMiddleware(object):
    # Not all methods need to be defined. If a method is not defined,
    # scrapy acts as if the downloader middleware does not modify the
    # passed objects.

    @classmethod
    def from_crawler(cls, crawler):
        # This method is used by Scrapy to create your spiders.
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    def process_request(self, request, spider):
        # Called for each request that goes through the downloader
        # middleware.

        # Must either:
        # - return None: continue processing this request后续中间件继续下载
        # - or return a Response object停止process_request,开始执行process_response
        # - or return a Request object停止中间件,重新调度Request
        # - or raise IgnoreRequest: process_exception() methods 执行process_exception
        #   installed downloader middleware will be called
        return None

    def process_response(self, request, response, spider):
        # Called with the response returned from the downloader.

        # Must either;
        # - return a Response object
        # - return a Request object
        # - or raise IgnoreRequest
        return response

    def process_exception(self, request, exception, spider):
        # Called when a download handler or a process_request()
        # (from other downloader middleware) raises an exception.

        # Must either:
        # - return None: continue processing this exception
        # - return a Response object: stops process_exception() chain
        # - return a Request object: stops process_exception() chain
        pass

    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)


下面是持久化数据的操作,使用的是twisetd支持的异步引擎提高存储速度,并且数据库相关配置信息从setting文件里面读取:

# -*- coding: utf-8 -*-
"""
pipeline.py
"""

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
from twisted.enterprise import adbapi
from pymysql import cursors


class JianshuSpiderPipeline(object):
    def __init__(self, conn):
        self.conn = conn
        self.conn.update({'cursorclass':cursors.DictCursor})
        print('===========>',self.conn)
        self.dbpool = adbapi.ConnectionPool('pymysql', **self.conn)
        self._sql = None

    @classmethod
    def from_crawler(cls, crawler):#创建对象读取配置文件
        conn_info = crawler.settings.get('DB')
        return cls(conn_info)

    @property
    def sql(self):
        if not self._sql:
            # self._sql = """insert into article(id, title, content, article_id, author, origin_url, avatar) values(null, %s, %s, %s, %s, %s,%s)"""
            self._sql = """insert into article(id, title, content, article_id, author, origin_url) values(null, %s, %s, %s, %s, %s)"""
        return self._sql

    def process_item(self, item, spider):
        defer = self.dbpool.runInteraction(self._insert_item, item)
        defer.addErrback(self._handle_error, item, spider)
        return item #交给下一个pipeline
        # from scrapy.exceptions import DropItem
        # raise DropItem()#丢弃(信号可以捕捉到),不给下一个pipeline

    def _insert_item(self, cursor, item):
        params = (item['title'],
                  item['content'],
                  item['article_id'],
                  item['author'],
                  item['origin_url']
                  )
        cursor.execute(self.sql, params)

    def _handle_error(self, failue, item, spider):
        print('failue=====>', failue)

    def open_spider(self, spider):
        pass

    def close_spider(self, spider):
        pass

下面是setting配置文件,里面有部分配置的注释

# -*- coding: utf-8 -*-

# Scrapy settings for jianshu_spider project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
#     https://doc.scrapy.org/en/latest/topics/settings.html
#     https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
#     https://doc.scrapy.org/en/latest/topics/spider-middleware.html

BOT_NAME = 'jianshu_spider'

SPIDER_MODULES = ['jianshu_spider.spiders']
NEWSPIDER_MODULE = 'jianshu_spider.spiders'


# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'jianshu_spider (+http://www.yourdomain.com)'

# Obey robots.txt rules
#ROBOTSTXT_OBEY = True

# Configure maximum concurrent requests performed by Scrapy (default: 16)
#CONCURRENT_REQUESTS = 32 #最大发送请求数量,,默认16

# Configure a delay for requests for the same website (default: 0)
# See https://doc.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
DOWNLOAD_DELAY = 3 #访问频率控制(s)
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16每个域名最多发送请求数量
#CONCURRENT_REQUESTS_PER_IP = 16每个ip并发数量

# Disable cookies (enabled by default)
#COOKIES_ENABLED = False请求携带cookie
#COOKIES_DEBUG = False

# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = False#监听爬虫当前状态和指标,开始暂停等

# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
  'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
  'Accept-Language': 'en',
  'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
}

# Enable or disable spider middlewares
# See https://doc.scrapy.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
#    'jianshu_spider.middlewares.JianshuSpiderSpiderMiddleware': 543,
#}

# Enable or disable downloader middlewares
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
#    'jianshu_spider.middlewares.JianshuSpiderDownloaderMiddleware': 543,
#}



# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
   'jianshu_spider.pipelines.JianshuSpiderPipeline': 300,
}

# Enable and configure the AutoThrottle extension (disabled by default)
# See https://doc.scrapy.org/en/latest/topics/autothrottle.html
#智能模拟请求,限速
#AUTOTHROTTLE_ENABLED = True
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
#AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
#AUTOTHROTTLE_DEBUG = False

# Enable and configure HTTP caching (disabled by default)
# See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
#HTTPCACHE_ENABLED = True启用缓存(这个地方可以自定义)
#HTTPCACHE_EXPIRATION_SECS = 0#超时时间
#HTTPCACHE_DIR = 'httpcache'#缓存目录
#HTTPCACHE_IGNORE_HTTP_CODES = []#忽略的状态码
#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'#所有请求都缓存

#定义爬取深度
DEPTH_LIMIT = 2
DEPTH_PRIORITY = 0#广度或者深度优先
#自定义去重方式
#默认方式DUPEFILTER_CLASS = "scrapy.dupefilters.RFPDupeFilter"
DUPEFILTER_CLASS = "jianshu_spider.duplication.RepeatFilter"


##########DB setting ############
DB = {  #key务必大写
'host': '127.0.0.1',
'port': 3306,
'user' : 'root',
'password' : 'pwd',
'database' : 'jianshu',
'charset' : 'utf8'
}

##########扩展钩子################
# from scrapy.extensions import telnet  扩展实例
# from scrapy import signals  扩展汇总

# EXTENSIONS={
#     'jianshu_spider.extensions.MyExtend':300
# }

# Enable or disable extensions
# See https://doc.scrapy.org/en/latest/topics/extensions.html
#EXTENSIONS = {
#    'scrapy.extensions.telnet.TelnetConsole': None,
#}

这里自定义了一个去重的方式,主要是为了学习这里去重的原理,自定义去重后需要在配置文件定义

# -*- coding: utf-8 -*-

"""
dont_filter=False  默认基于内存或者文件去重
from scrapy.dupefilters import RFPDupeFilter

自定义去重也可以基于数据库操作
open:可以加入数据库操作
close:关闭数据库操作
"""
from scrapy.dupefilters import BaseDupeFilter

class RepeatFilter(BaseDupeFilter):
    """Request Fingerprint duplicates filter"""

    def __init__(self):
        self.visited_set = set()

    @classmethod
    def from_settings(cls, settings):
        return cls()

    def request_seen(self, request):
        if request.url in self.visited_set:
            return True
        self.visited_set.add(request.url)
        return False

    def open(self):
        pass

    def close(self, reason):
        pass

启动爬虫,可以看到数据库已经开始存入数据:


image.png
上一篇下一篇

猜你喜欢

热点阅读