scrapy
scrapy 是一个爬取网站数据, 提取结构性数据的框架. 注意敲重点是框架. 框架就说明了什么?-- 提供的组件丰富, scrapy 的设计参考了 Django, 可见一斑. 但是不同于 Django 的是 scrapy 的可拓展性也很强, 所以说, 你说你会用 python 写爬虫, 不了解点 scrapy....
scrapy 使用了 Twisted 异步网络库来处理网络通讯, 整体架构如下图:
Scrapy 主要包括了以下组件:
引擎 (Scrapy)
用来处理整个系统的数据流处理, 触发事务 (框架核心)
调度器 (Scheduler)
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个 URL(抓取网页的网址或者说是链接) 的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
下载器 (Downloader)
用于下载网页内容, 并将网页内容返回给蜘蛛 (Scrapy 下载器是建立在 twisted 这个高效的异步模型上的)
爬虫 (Spiders)
爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体 (Item). 用户也可以从中提取出链接, 让 Scrapy 继续抓取下一个页面
项目管道 (Pipeline)
负责处理爬虫从网页中抽取的实体, 主要的功能是持久化实体, 验证实体的有效性, 清除不需要的信息. 当页面被爬虫解析后, 将被发送到项目管道, 并经过几个特定的次序处理数据.
下载器中间件 (Downloader Middlewares)
位于 Scrapy 引擎和下载器之间的框架, 主要是处理 Scrapy 引擎与下载器之间的请求及响应.
爬虫中间件 (Spider Middlewares)
介于 Scrapy 引擎和爬虫之间的框架, 主要工作是处理蜘蛛的响应输入和请求输出.
调度中间件 (Scheduler Middewares)
介于 Scrapy 引擎和调度之间的中间件, 从 Scrapy 引擎发送到调度的请求和响应.
好介绍到这里. 面试题来了: 谈谈你对 scrapy 架构的理解:
上面就是我的理解, 你的理解与我的可能不一样, 以你的为准, 面试的时候如果被问到了. 千万要说, 这东西怎么说都对. 不说的话, 一定会被面试官反问一句: 你还有什么要补充的吗?....
安装:
pass
直接开始写代码吧.
项目上手, 利用 scrapy 爬取抽屉. 读到这里你是不是有点失望了? 为什么不是淘宝? 不是腾讯视频? 不是知乎? 在此说明下抽屉虽小, 五脏俱全嘛. 目的是为了展示下 scrapy 的基本用法没必要将精力花在处理反爬虫上.
在开始我们的项目之前, 要确定一件事就是我们要爬取什么数据? 怎么存储数据? 这个案例使用到的 MySQL 最为容器存储数据的. 设计的表结构如下:
有了这些字段就能着手于 spider 的编写了, 第一步首先写 item 类
- import datetime
- import re
- import scrapy
- from scrapy.loader import ItemLoader
- from scrapy.loader.processors import Join, MapCompose, TakeFirst
- def add_title(value):
- """字段之后加上签名"""
- return value + 'Pontoon'
- class ArticleItemLoader(ItemLoader):
- """重写 ItemLoader, 从列表中提取第一个字段"""
- default_output_processor = TakeFirst()
- class ChouTiArticleItem(scrapy.Item):
- """初始化 items"""
- title = scrapy.Field(
- input_processor=MapCompose(add_title)
- )
- article_url = scrapy.Field()
- article_url_id = scrapy.Field()
- font_img_url = scrapy.Field()
- author = scrapy.Field()
- sign_up = scrapy.Field()
- def get_insert_sql(self):
- insert_sql = '''
- insert into chouti(title, article_url, article_url_id, font_img_url, author, sign_up)
- VALUES (%s, %s, %s, %s, %s, %s)
- ''' params = (self["title"], self["article_url"], self["article_url_id"], self["font_img_url"],
- self["author"], self["sign_up"],)
- return insert_sql, params
items.py
下一步解析数据 (这里并没由对下一页进行处理, 省事!)
- # -*- coding: utf-8 -*-
- import scrapy
- from scrapy.http import Request
- from ..items import ChouTiArticleItem, ArticleItemLoader
- from ..utils.common import get_md5
- class ChoutiSpider(scrapy.Spider):
- name = 'chouti'
- allowed_domains = ['dig.chouti.com/']
- start_urls = ['https://dig.chouti.com/']
- header = {
- "User-Agent": "Mozilla/5.0 (Windows NT 6.3; Win64; x64) ApplewebKit/537.36 (Khtml, like Gecko)"
- "Chrome/71.0.3578.98 Safari/537.36"
- }
- def parse(self, response):
- article_list = response.xpath("//div[@class='item']")
- for article in article_list:
- item_load = ArticleItemLoader(item=ChouTiArticleItem(), selector=article) # 注意这里的返回值并不是 response 了
- article_url = article.xpath(".//a[@class='show-content color-chag']/@href").extract_first("")
- item_load.add_xpath("title", ".//a[@class='show-content color-chag']/text()")
- item_load.add_value("article_url", article_url)
- item_load.add_value("article_url_id", get_md5(article_url))
- item_load.add_xpath("font_img_url", ".//div[@class='news-pic']/img/@src")
- item_load.add_xpath("author", ".//a[@class='user-a']//b/text()")
- item_load.add_xpath("sign_up", ".//a[@class='digg-a']//b/text()")
- article_item = item_load.load_item() # 解析上述定义的字段
- yield article_item
spiders.py
OK, 这时我们的数据就被解析成了字典 yield 到了 Pipline 中了
- import MySQLdb
- import MySQLdb.cursors
- from twisted.enterprise import adbapi # 将入库变成异步操作
- class MysqlTwistedPipleline(object):
- """抽屉 Pipleline"""
- def __init__(self, db_pool):
- self.db_pool = db_pool
- @classmethod
- def from_settings(cls, settings):
- """内置的方法自动调用 settings"""
- db_params = dict(
- host=settings["MYSQL_HOST"],
- db=settings["MYSQL_DBNAME"],
- user=settings["MYSQL_USER"],
- password=settings["MYSQL_PASSWORD"],
- charset="utf8",
- cursorclass=MySQLdb.cursors.DictCursor,
- use_unicode=True,
- )
- db_pool = adbapi.ConnectionPool("MySQLdb", **db_params)
- return cls(db_pool)
- def process_item(self, item, spider):
- """使用 twisted 异步插入数据值数据库"""
- query = self.db_pool.runInteraction(self.do_insert, item) # runInteraction() 执行异步操作的函数
- query.addErrback(self.handle_error, item, spider) # addErrback() 异步处理异常的函数
- def handle_error(self, failure, item, spider):
- """自定义处理异步插入数据的异常"""
- print(failure)
- def do_insert(self, cursor, item):
- """自定义执行具体的插入"""
- insert_sql, params = item.get_insert_sql()
- # chouti 插入数据
- cursor.execute(insert_sql, (item["title"], item["article_url"], item["article_url_id"],
- item["font_img_url"], item["author"], item["sign_up"]))
piplines.py
再到 settings.py 中配置一下文件即可.
- ITEM_PIPELINES = {
- 'ChoutiSpider.pipelines.MysqlTwistedPipleline': 3,
- }
- ...
- MYSQL_HOST = "127.0.0.1"
- MYSQL_DBNAME = "article_spider" # 数据库名称
- MYSQL_USER = "xxxxxx"
- MYSQL_PASSWORD = "xxxxx"
settings.py
ok 练手的项目做完了, 接下来正式进入正题.
scrapy-pipeline 组件
pipeline 是用来做数据的持久化的, 内部源码中给我们提供一些方法, 下面介绍几种常见的.
- from scrapy.exceptions import DropItem
- class CustomPipeline(object):
- def __init__(self,v):
- self.value = v
- def process_item(self, item, spider):
- # 操作并进行持久化
- # return 表示会被后续的 pipeline 继续处理
- return item
- # raise DropItem() 表示将 item 丢弃, 不会被后续 pipeline 处理
- @classmethod
- def from_crawler(cls, crawler):
- """
- 初始化时候, 用于创建 pipeline 对象
- :param crawler:
- :return:
- """ val = crawler.settings.getint('XXXXX')
- return cls(val) # 注意这种创建对象的方式, 在 scrapy 源码中会大量遇见
- def open_spider(self,spider):
- """
- 爬虫开始执行时, 调用
- :param spider:
- :return:
- """ print('spider start')
- def close_spider(self,spider):
- """
- 爬虫关闭时, 被调用
- :param spider:
- :return:
- """ print('spider close')
pipeline.py
scrapy-url 去重的策略
url 去重的问题用于分布式爬虫.
来源: http://www.bubuko.com/infodetail-2914757.html