(转)Scrapy总结(一)---框架详解

原文:https://juejin.cn/post/6908717173486092302

https://juejin.cn/post/6908929559530471431------Scrapy总结(二)---settings详解

https://juejin.cn/post/6914536700035203085-------scrapy总结(三)---Splash的安装与使用

https://juejin.cn/post/6916875761865457672-------scrapy总结(四)---Scrapy-redis分布式爬虫

1. Scrapy框架介绍

  • Scrapy是Python开发的一个快速高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。
  • Scrapy = Scrach+Python·Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试、信息处理和历史档案等大量应用范围内抽取结构化数据的应用程序框架,广泛用于工业
  • Scrapy使用Twisted这个异步网络库来处理网络通讯,架构清晰,并且包含了各种中间件接口,可以灵活的完成各种需求。Scrapy是由Twisted写的一个受欢迎的Python事件驱动网络框架,它使用的是非堵塞的异步处理

1.1 为什么要使用Scrapy?

  • 它更容易构建和大规模的抓取项目
  • 它内置的机制被称为选择器,用于从网站(网页)上提取数据·它异步处理请求,速度十分快
  • 它可以使用自动调节机制自动调整爬行速度·确保开发人员可访问性

1.2 Scrapy的特点

  • Scrapy是一个开源和免费使用的网络爬虫框架

  • Scrapy生成格式导出如:JSON,CSV和XML

  • Scrapy内置支持从源代码,使用XPath或CSS表达式的选择器来提取数据.

  • Scrapy基于爬虫,允许以自动方式从网页中提取数据

1.3 Scrapy的优点

  • Scrapy很容易扩展,快速和功能强大;
  • 这是一个跨平台应用程序框架(在Windows,Linux,Mac OS和BSD)。. Scrapy请求调度和异步处理;
  • Scrapy附带了一个名为Scrapyd的内置服务,它允许使用JSON Web服务上传项目和控制蜘蛛。也能够刮削任何网站,即使该网站不具有原始数据访问API

1.4 框架概览

img

  1. 引擎(EGINE)

    引擎负责控制系统所有组件之间的数据流,并在某些动作发生时触发事件。有关详细信息,请参见上面的数据流部分。

  2. 调度器(SCHEDULER) 用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL的优先级队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址

  3. 下载器(DOWLOADER) 用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的

  4. 爬虫(SPIDERS) SPIDERS是开发人员自定义的类,用来解析responses,并且提取items,或者发送新的请求

  5. 项目管道(ITEM PIPLINES) 在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作

  6. 下载器中间件(Downloader Middlewares)

    位于Scrapy引擎和下载器之间,主要用来处理从EGINE传到DOWLOADER的请求request,已经从DOWNLOADER传到EGINE的响应response,你可用该中间件做以下几件事

    1. process a request just before it is sent to the Downloader (i.e. right before Scrapy sends the request to the website);
    2. change received response before passing it to a spider;
    3. send a new Request instead of passing received response to a spider;
    4. pass response to a spider without fetching a web page;
    5. silently drop some requests.
  7. 爬虫中间件(Spider Middlewares) 位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入(即responses)和输出(即requests)

2 安装

pip install scrapy
复制代码

使用conda请查看勇哥的ID文章--conda的安装juejin.cn/post/690640…

pip安装不上的包在此网站www.lfd.uci.edu/~gohlke/pyt…

pip install 文件路径
复制代码

要是项目文件中包有缺失,请看下面(windowsx系统)

pip install whell lxml pyopenssl twisted scrapy
复制代码

要是linux系统的话直接pip install scrapy

3 创建一个scrapy项目

scrapy createproject myfirst
cd myfirst
scrapy genspider douban douban.com  # 创建一个爬虫

复制代码
名称作用
scrapy.cfg 项目的配置信息,主要为Scrapy命令行工具提供一个基础的配置信息。(真正爬虫相关的配置信息在settings.py文件中)
items.py 设置数据存储模板,用于结构化数据,如: Django的Model
pipelines 数据处理行为,如:—般结构化的数据持久化
settings.py 配置文件,如:递归的层数、并发数,延迟下载等
spider 爬虫目录,如:创建文件,编写爬虫规则

新建一个爬虫文件之后,可以看见文件的目录,比如新建一个baidu的爬虫,code~

import scrapy


class BaiduSpider(scrapy.Spider):
    name = 'baidu'  # 爬虫的名字
    allowed_domains = ['baidu.com']  # 允许爬取的url
    start_urls = ['http://baidu.com/']  # 开始解析的url

    # parse开始响应
    def parse(self, response):
        pass

复制代码

settings.py注解

# 我的第一个项目的Scrapy设置
#
# 为简单起见,此文件只包含被认为重要或的设置
# 常用的。你可以在文档中找到更多的设置
#
#     https://docs.scrapy.org/en/latest/topics/settings.html
#     https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#     https://docs.scrapy.org/en/latest/topics/spider-middleware.html

BOT_NAME = 'myfirst'

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

# 在用户代理(user-agent)上识别你自己(和你的网站),负责任地爬行
# USER_AGENT = 'myfirst (+http://www.yourdomain.com)'

# 遵守robots . txt规则
ROBOTSTXT_OBEY = True

# 配置Scrapy执行的最大并发请求(默认值:16)
# CONCURRENT_REQUESTS = 32

# 为同一网站的请求配置延迟(默认值:0)
# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
# 参见自动油门设置和文档
# DOWNLOAD_DELAY = 3
# 下载延迟设置将只支持以下其中一个:
# CONCURRENT_REQUESTS_PER_DOMAIN = 16
# CONCURRENT_REQUESTS_PER_IP = 16

# 禁用cookie(默认启用)
# COOKIES_ENABLED = False

# 禁用Telnet控制台(默认启用)
# TELNETCONSOLE_ENABLED = False

# 覆盖默认的请求头:
# DEFAULT_REQUEST_HEADERS = {
#   'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
#   'Accept-Language': 'en',
# }

# 启用或禁用蜘蛛中间件
# See https://docs.scrapy.org/en/latest/topics/spider-middleware.html
# SPIDER_MIDDLEWARES = {
#    'myfirst.middlewares.MyfirstSpiderMiddleware': 543,
# }

# 启用或禁用下载器中间件
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
# DOWNLOADER_MIDDLEWARES = {
#    'myfirst.middlewares.MyfirstDownloaderMiddleware': 543,
# }

# 启用或禁用扩展
# See https://docs.scrapy.org/en/latest/topics/extensions.html
# EXTENSIONS = {
#    'scrapy.extensions.telnet.TelnetConsole': None,
# }

# 配置项管道
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# ITEM_PIPELINES = {
#    'myfirst.pipelines.MyfirstPipeline': 300,
# }

# 启用和配置自动油门扩展(默认禁用)
# See https://docs.scrapy.org/en/latest/topics/autothrottle.html
# AUTOTHROTTLE_ENABLED = True
# 初始下载延迟
# AUTOTHROTTLE_START_DELAY = 5
# 在高延迟的情况下设置的最大下载延迟
# AUTOTHROTTLE_MAX_DELAY = 60
# Scrapy应该并行发送到每个远程服务器的平均请求数
# AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# 为收到的每个响应显示节流统计
# AUTOTHROTTLE_DEBUG = False

# 启用和配置HTTP缓存(默认禁用)
# See https://docs.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'

复制代码

启动爬虫程序

scrapy crawl baidu
复制代码

我们要debug的话如何运行程序

# run.py
import scrapy.cmdline import execute
execute(['scrapy','crawl','baidu'])

复制代码

4 Scrapy数据的提取

1 选择器有四个基本的方法,如下所示:

S.N.方法&描述
get() 始终返回单个结果;如果有多个匹配项,则返回第一个匹配项的内容;如果没有匹配项,则不返回任何匹配项。
getall() 返回包含所有结果的列表
re() 它返回Unicode字符串列表,当正则表达式被赋予作为参数时提取
xpath() 它返回选择器列表,它代表由指定XPath表达式参数选择的节点
css() 它返回选择器列表,它代表由指定CSS表达式作为参数所选择的节点

4.1 Scrapy Shell

如果使用选择器想快速的到到效果,我们可以使用Scrapy Shell

scrapy shell "http: / / www . 163.com"
复制代码

举一个栗子

scrapy genspider qidian qidian.com  # 创建一个爬虫


复制代码

4.2 保存数据到json/xml/csv文件

# 获取七点中文网的作者名字和书名,并写入json文件
import scrapy


class QidianSpider(scrapy.Spider):
    name = 'qidian'
    allowed_domains = ['qidian.com']
    start_urls = ['https://www.qidian.com/rank/yuepiao']

    def parse(self, response):
        names = response.xpath('//h4/a/text()').getall()  # 所有的书籍名称
        authors = response.xpath('//p[@class="author"]/a/text()').getall()  # 所有的作者
        book = []
        for name, author in (zip(names, authors)):
            book.append({'name': name, 'author': author})
        return book  # 一定要return 这个列表才能保存到json文件


# 然后在终端中执行命令
scrapy crawl qidian -o book.json  # 保存成json

scrapy crawl qidian -o book.xml  # 保存成xml

scrapy crawl qidian -o book.csv  # 保存成csv
复制代码

5 scrapy中pipline的使用

scrapy genspider maoyan maoyan.com

复制代码

string()会把当前节点和所有的子孙节点中的文本全部提取出来,组合成一个字符串。

首先将settings.py中的ITEM_PIPELINES注释打开,系统会给我们默认生成一个piplines的配置器

ITEM_PIPELINES = {
   'myfirst.pipelines.MyfirstPipeline': 300,
}
复制代码

spider能想管道中yield两个格式的数据,一个是字典,一个是item对象,这个item对象就是item.py中定义的类,spider中返回的数据要和类对象一一对应

# items.py

class VideoItem(scrapy.Item):
    name = scrapy.Field()
    source = scrapy.Field()
    
# maoyan.py
import scrapy

from myfirst.items import VideoItem


class MaoyanSpider(scrapy.Spider):
    name = 'maoyan'
    allowed_domains = ['maoyan.com']
    start_urls = ['https://maoyan.com/films']

    def parse(self, response):
        names = response.xpath(
            '//div[@class="channel-detail movie-item-title"]/@title').getall()
        sources = [source.xpath('string(.)').get() for source in
                   response.xpath(
                       '//div[@class="channel-detail channel-detail-orange"]')]

        # 1 直接yield字典
        # for name, source in zip(names, sources):
        #     yield {"name": name, "source": source}

        # 2 生成一个videoItem实例
        video_item = VideoItem()
        for name, source in zip(names, sources):
            video_item['name'] = name
            video_item['source'] = source
            yield video_item

# piplines.py
import json

from itemadapter import ItemAdapter


class MyfirstPipeline:
    def open_spider(self, spider):
        self.filename = open('movie.txt', 'w', encoding='utf-8')

    def process_item(self, item, spider):
        print(item)
        self.filename.write(json.dumps(dict(item), ensure_ascii=False) + "\n")

    def close_spider(self, spider):
        self.filename.close()
        
        


复制代码

在这里说说一嘴,要是yield到item中的是字典,那么就piplines中就不需要转格式

要是yield到item中的是一个item对象,就需要在piplines中process_item方法中dict(item)

6 爬虫实战

爬取小说网数据 gir仓库gitee.com/ouyangguoyo…

为了保证爬取效率,先分别下载到单独的文件中,然后按照序号命名,最后合并到一起

# qiankun_new.py
import scrapy

from fiction.items import StoreItem
from util import ch2num, merge


# from concurrent.futures import ThreadPoolExecutor

class QiankunNewSpider(scrapy.Spider):
    name = 'qiankun_new'
    allowed_domains = ['kunnu.com']
    start_urls = ['https://www.kunnu.com/qiankun/']

    custom_settings = {
        'BOT_NAME': 'qiankun',
        '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 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36',
            'referer': 'https://www.kunnu.com/qiankun/',
            'cookie': '__cfduid=d6c080c2d1b5c482c8587927a277c9c341608550610; _ga=GA1.2.1450502414.1608550611; _gid=GA1.2.1799741753.1608550611'
        }
    }

    def start_requests(self):
        yield scrapy.Request(self.start_urls[0], dont_filter=True, callback=self.parse)

    # 爬虫结束了
    @staticmethod
    def close(spider, reason):
        # print(spider.nosearch)
        print("合并所有文件")
        merge()  # 合并所有文件
        return super().close(spider, reason)


    def parse(self, response):
        urls = response.xpath('//div[@class="book-list clearfix"]/ul/li/a/@href').getall()
        for url in urls[-5:]:
            print(url)
            yield scrapy.Request(url=url, callback=self.parse_content, dont_filter=False)

    def parse_content(self, response):
        title = response.xpath('//h1/text()').get()
        content = ''.join([i + "\n" for i in response.xpath('//div[@id="nr1"]/p/text()').getall()]).replace(
            '🐳 鲲~弩~小~说~w ww -k u n n u - Co m', '')
        store_item = StoreItem()
        store_item['title'] = title
        store_item['content'] = content
        yield store_item

        
# items.py
import scrapy


class FictionItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    pass


class StoreItem(scrapy.Item):
    title = scrapy.Field()
    content = scrapy.Field()

    
# piplines.py
import os

from itemadapter import ItemAdapter

from util import ch2num


class FictionPipeline:
    def open_spider(self, spider):
        pass

    def process_item(self, item, spider):
        item = dict(item)
        title = item.get('title').split(' ')[0]
        content = item.get('content')
        sort = ch2num(title[1:-1]) if title != "结局感言" else 1308
        path = "F:/learn scrapy/fiction/武动乾坤/" + f'{str(sort)}.txt'
        if not os.path.exists(path):
            with open(path, 'w', encoding="utf-8") as f:
                f.write(title + "\n" + content)

    def close_spider(self, spider):
        pass

    
    # util.py
    
    import os

chinese_number_dict = {'一':1, '七':7, '万':10000, '三':3, '九':9,'两':2, '二':2, '五':5, '八':8, '六':6, '十':10,'三':3, '千':1000, '四':4, '百':100, '零':0,"亿":100000000}
not_in_decimal = "十百千万亿点"
def ch2num(chstr):
    if '点' not in chstr:
        return ch2round(chstr)
    splits = chstr.split("点")
    if len(splits) != 2:
        return splits
    rount = ch2round(splits[0])
    decimal = ch2decimal(splits[-1])
    if rount is not None and decimal is not None:
        return float(str(rount) + "." + str(decimal))
    else:
        return None


def ch2round(chstr):
    no_op = True
    if len(chstr) >= 2:
        for i in chstr:
            if i in not_in_decimal:
                no_op = False
    else:
        no_op = False
    if no_op:
        return ch2decimal(chstr)

    result = 0
    now_base = 1
    big_base = 1
    big_big_base = 1
    base_set = set()
    chstr = chstr[::-1]
    for i in chstr:
        if i not in chinese_number_dict:
            return None
        if chinese_number_dict[i] >= 10:
            if chinese_number_dict[i] > now_base:
                now_base = chinese_number_dict[i]
            elif now_base >= chinese_number_dict["万"] and now_base < chinese_number_dict["亿"] and chinese_number_dict[i] > big_base:
                now_base = chinese_number_dict[i] * chinese_number_dict["万"]
                big_base = chinese_number_dict[i]
            elif now_base >= chinese_number_dict["亿"] and chinese_number_dict[i] > big_big_base:
                now_base = chinese_number_dict[i] * chinese_number_dict["亿"]
                big_big_base = chinese_number_dict[i]
            else:
                return None
        else:
            if now_base in base_set and chinese_number_dict[i] != 0:
                return None
            result = result + now_base * chinese_number_dict[i]
            base_set.add(now_base)
    if now_base not in base_set:
        result = result + now_base * 1
    return result

def ch2decimal(chstr):
    result = ""
    for i in chstr:
        if i in not_in_decimal:
            return None
        if i not in chinese_number_dict:
            return None
        result = result + str(chinese_number_dict[i])
    return int(result)


def merge():
    folder = os.path.join(os.path.dirname(os.path.abspath(__file__)),'武动乾坤')
    # 按照名字排序
    files = sorted(os.listdir(folder),key=lambda x:int(x[0:-4]),reverse=False)
    with open('wudongqiankun.txt', 'r+', encoding='utf-8') as f_l:
        f_l.truncate()  # 清空数据

    f_base = open('wudongqiankun.txt','a',encoding='utf-8')
    for file in files:
        with open(os.path.join(folder,file),'r',encoding='utf-8') as f:
            f_read = "\n"*3 + f.read()
        f_base.write(f_read)
        f_base.flush()
    f_base.close()






复制代码

执行

from scrapy.cmdline import execute

# execute('scrapy crawl qiankun'.split())
execute('scrapy crawl qiankun_new'.split())
posted @   liujiacai  阅读(297)  评论(0编辑  收藏  举报
编辑推荐:
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
阅读排行:
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
历史上的今天:
2018-09-20 (转)python 判断数据类型
点击右上角即可分享
微信分享提示