wking's blog

  • 文章分类
    • 日常随笔
    • IT技术
    • 系统封装
    • 大航海时代
  • 关于博主
GOD'S IN HIS HEAVEN, ALL'S RIGHT WITH THE WORLD.
  1. 首页
  2. IT技术
  3. 正文

用scrapy镜像全站

2022-04-19 2163点热度 1人点赞 0条评论

准备工作

网上包括官网讲的都遮遮掩掩的。还是得靠自己,研究了一阵子,做个笔记。

用pycharm调试scrapy

网上大部分教程说的都是新建一个py文件,py里写cmdline.execute("scrapy crawl spider".split())类似语句。这样也可以,但还是得新建个py文件,每次调试时必须从这个文件启动调试,不方便,而且也不利于git。

我用pycharm自带的模块调用法,更方便。

第一步,先在pycharm里打开项目,点击菜单-运行-编辑配置,选择+号新建python配置。
名称:不填的话,pycharm会自动填模块名,也可以自己填一个。记住这个名字。
模块名称:固定写 scrapy.cmdline
参数:crawl固定,debug是你的蜘蛛name,--nolog是不显示scrapy调试信息,可有可无
解释器:选择你装了scrapy的,你希望用的虚拟环境解释器
工作目录:选择scrapy项目的目录,也就是里面有scrapy.cfg文件的那个目录

pycharm 添加scrapy调试环境

第二步,pycharm里打开你想要调试的scrapy文件,右上角调试环境下拉列表里,选择上一步新建的名称,也就是刚刚建立的调试环境。然后点右边的小蜘蛛按钮,或者按SHIFT+F9,或运行菜单-顶部第二个调试选择,就开始正常调试过程。只要是这个scrapy项目里的py文件,都直接点小蜘蛛即可调试。想要更换蜘蛛,就把调试环境里的debug改一下就行。

选择调试环境,开始调试

不能选择右键菜单里的调试,那个调试会自动新建一个针对这个文件的调试环境,对普通py文件可以,对scrapy无效。

不能选择右键菜单里的调试

代码性能比较

response.xpath和beautifulsoup性能比较

解析网页提取链接时有response.xpath和beautifulsoup.find_all()两种选择,通过性能测试,response.xpath速度略快。

Python
1
2
3
4
5
6
7
8
bs = BeautifulSoup(response.body, 'lxml')
for i in range(10000):
    bs.find_all('a', href=True)
# 用时104秒。且每个网页BeautifulSoup都需要实例化
 
for i in range(10000):
    response.xpath('//a/@href').getall()
# 用时37秒。response是scrapy自带,无需额外操作

BeautifulSoup.find_all()方法测试

使用BeautifulSoup的find_all()方法选取HTML网页内容标签时,find_all()可以传入单独标签或标签列表。经过测试,两者性能差别不大,但是单独标签在编写的时候更直观,好修改。

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bs = BeautifulSoup(file_obj, 'lxml')
 
start = time.time()
for i in range(1000):
    bs.find_all('a', href=True)
    bs.find_all('img', src=True)
    bs.find_all('link', href=True)
    bs.find_all('script')
print(f"time:{time.time() - start}")
# time:161.9159119129181
 
start = time.time()
for i in range(1000):
    bs.find_all(['a', 'img', 'link', 'script'])
print(f"time:{time.time() - start}")
# time:137.75350737571716

使用列表形式的find_all()时,可以通过find_all()方法的name属性确定具体是哪一个标签。

Python
1
2
3
4
5
6
7
8
9
10
11
12
tags = bs.find_all(['a', 'img', 'link', 'script'])
for tag in tags:
# 每个tag都有自己的名字,通过 .name 来获取
# https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/#name
    if tag.name == 'a':
        pass
    elif tag.name == 'img':
        pass
    elif tag.name == 'link':
        pass
    elif tag.name == 'script':
        pass

BeautifulSoup的select_one()方法和find()方法性能比较

使用BeautifulSoup选取唯一的标签时,有select_one()方法和find()方法两种实现方法。经过测试对比,find()比select_one()快一倍。

select_one()方法和find()方法性能比较
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
bs = BeautifulSoup(file_obj, 'lxml')
 
start = time.time()
for i in range(10000):
    bs.select_one('div[id="abc"]')
print(f"\n{time.time() - start}")
# 9.24462080001831秒
 
start = time.time()
for i in range(10000):
    bs.find('div', id='abc')
print(f"\n{time.time() - start}")
# 5.53403115272522秒

python使用多进程加快处理速度

这里说的是多进程,而不是多线程。由于GIL锁的存在,在windows下python的多线程除了让代码变的复杂之外毫无作用。而多进程就不同了,只要你CPU够强大,开多少个处理进程都可以,而且开的进程越多,缩短的时间越多。理论上有个公式:N个进程运行时间=1个进程的时间 / N。比如正常运行一个py脚本需要10分钟,那么开2进程需要10/2=5分钟,开4进程需要10/4=2.5分钟。但多进程的缺点是各个进程之间无法通信,数据不共享,除非你额外编一个调度通信程序,不过每个进程运行完之后,结果是可以返回汇总的。

简单的多进程不难,代码模型如下:

python多进程模型
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import os
from multiprocessing import Pool, RLock, freeze_support
from tqdm import tqdm
 
def 多进程处理函数(list):
    pass
    return data # 你要返回的数据,数据类型任意,会存在pool_result里
 
if __name__ == '__main__':
        要处理的列表 = []
 
        # 多进程
        # print('Parent process %s' % os.getpid())
        t_num = os.cpu_count() - 2  # 进程数 读取CPU逻辑处理器个数
        freeze_support()  # for Windows support
        tqdm.set_lock(RLock())  # for managing output contention
        p = Pool(processes=t_num, initializer=tqdm.set_lock, initargs=(tqdm.get_lock(),))
        pool_result = []  # 存放pool池的返回对象列表
        for i in range(0, t_num):
            div = int(len(要处理的列表) / t_num)
            mod = len(要处理的列表) % t_num
            if i + 1 != t_num:
                # print(i, i * div, (i + 1) * div)
                pool_result.append(p.apply_async(多进程处理函数, args=(要处理的列表[i * div:(i + 1) * div], i)))
            else:
                # print(i, i * div, (i + 1) * div + mod)
                pool_result.append(
                    p.apply_async(多进程处理函数, args=(要处理的列表[i * div:(i + 1) * div + mod], i)))
 
        # print('Waiting for all subprocesses done...')
        p.close()
        p.join()
 
 
        # 读取pool的返回对象列表。i.get()是读取方法
        for i in pool_result:
            i.get()

使用了第三方库tqdm进度条,以便直观观看各进程进度。各进程处理后的结果会返回给pool_result列表。重点:进程流程控制代码段必须放在if name == 'main':里,每个进程的代码必须用函数或类包装,放在if name == 'main':外。这是死规定,跟windows底层有关。

下载图片

坑1 图片不下载

【这TM也太坑了】conda全新虚拟环境,安装最新scrapy(2.6.1),代码都对,无论怎么调试但图片就是不下载。原因是scrapy默认没安装下载图片所需的Pillow库。手动conda install Pillow即可。

坑2 gif图片下载后透明度、动态效果消失

scrapy代码是jpeg格式下载,即使你传递给scrapy的图片URL是gif图片,下载回来也变成了jpg格式的gif图片,透明度、动态效果都不见了。

需要重写ImagesPipeline图片管道,增强image_downloaded方法。因此,如果需要gif图片,只能使用自定义图片管道,不能用默认图片管道。如何建立自定义图片管道下面章节有介绍。

参考 https://zhuanlan.zhihu.com/p/81611461 重写ImagesPipeline图片管道,我将他的代码几个函数组合在一起了。

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import os
from scrapy.utils.misc import md5sum
class GvoImgPipeline(ImagesPipeline):
    # scrapy默认图片是jpeg格式下载,根据https://zhuanlan.zhihu.com/p/81611461 重写下载方法
    def image_downloaded(self, response, request, info):
        try:
            checksum = None
            for path, image, buf in self.get_images(response, request, info):
                if checksum is None:
                    buf.seek(0)
                    checksum = md5sum(buf)
                width, height = image.size
                if image.format is None:  # 是gif格式
                    absolute_path = self.store._get_filesystem_path(path)
                    self.store._mkdir(os.path.dirname(absolute_path), info)
                    f = open(absolute_path, 'wb')  # use 'b' to write binary data.
                    f.write(response.body)
                else:
                    self.store.persist_file(
                        path, buf, info,
                        meta={'width': width, 'height': height},
                        headers={'Content-Type': 'image/jpeg'})
                return checksum
        except:
            pass

使用scrapy默认图片管道下载图片

使用scrapy自带的ImagesPipeline管道。自己只需要在items.py里添加一个Item类,在你的蜘蛛spider parse方法里添加处理图片URL代码,在setting.py开启ImagesPipeline管道即可。不需要自己添加pipline管道。

items.py

添加一个Item类,内容固定,类名随意。

Python
1
2
3
4
5
6
7
class ImgItem(scrapy.Item):
    # 存放url的下载地址
    image_urls = scrapy.Field()
    # # 图片下载路径、url和校验码等信息(图片全部下载完成后将信息保存在images中)
    images = scrapy.Field()
    # # 图片的本地保存地址
    # image_paths = scrapy.Field()

image_urls、images变量名不能修改,否则scrapy无法识别图片信息。如果非要想修改,需要在settings.py里添加设置参数

Python
1
2
3
4
# # 该字段的值为XxxItem中定义的存储图片链接的image_urls字段
# IMAGES_URLS_FIELD = 'image_urls'
# # 该字段的值为XxxItem中定义的存储图片信息的images字段
# IMAGES_RESULT_FIELD = 'images'

你的蜘蛛文件

你的spider里,parse方法里添加图片URL解析语句,并最终传递给imgitem['image_urls']。传递的值必须是列表,这样才能用yield迭代。如果parse方法之前已经有yield response解析URL,也不影响。scrapy会先处理下载图片,然后再处理网页链接。

处理图片代码的核心思路就是先实例化ImgItem(),再把图片URL下载链接传递给imgitem['image_urls'],最后返回yield实例即可。scrapy内部检测到imgitem['image_urls']就会自动调用下载器去下载图片。

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from ..items import ImgItem
from bs4 import BeautifulSoup
def parse(self, response):
        # 新添加的 提取图片
        imgitem = ImgItem()
        imglist = []
        bs = BeautifulSoup(response.body, 'lxml')
        for img in bs.select('img'):
            if 'src' in img.attrs:
                imglist.append(img.attrs['src'])
        imgitem['image_urls'] = list(set(imglist))  # 列表去重后传递
        yield imgitem  # 返回item先交给ImagesPipeline下载图片
 
        # 之前的代码,提取本页链接继续解析
        urls = response.xpath('//a/@href').getall()
        if urls:
            yield from response.follow_all(urls, callback=self.parse)

settings.py

添加图片保存位置参数,开启scrapy自带的图片管道。

Python
1
2
3
4
IMAGES_STORE = './img'
ITEM_PIPELINES = {
    'scrapy.pipelines.images.ImagesPipeline': 1,
    }

图片将会保存在你的项目目录的img文件夹。

ImagesPipeline': 1的意思是这个管道优先级1,最高。

使用自定义管道下载图片

和使用scrapy默认图片管道方法基本一致,除了settings.py不同。参照默认图片管道修改item.py、蜘蛛文件。

使用自定义管道的好处是可以自由定义图片下载流程和设置。比如自定义图片文件名。

piplines.py

新建一个pipline,且继承ImagesPipeline。这里的代码只修改图片保存位置和文件名。其他功能参考https://docs.scrapy.org/en/latest/topics/media-pipeline.html#module-scrapy.pipelines.files

Python
1
2
3
4
5
6
7
8
9
from urllib.parse import urlparse
from scrapy.pipelines.images import ImagesPipeline
 
class GvoImgPipeline(ImagesPipeline):
    def file_path(self, request, response=None, info=None, *, item=None):
        # 保存到 settings.py.IMAGES_STORE + urlparse(request.url).path 目录
        # urlparse(request.url) = ParseResult(scheme='https', netloc='www.baidu.com', path='/WIKI/61/00016461.JPG', params='', query='', fragment='')
        # urlparse(request.url).path = '/WIKI/61/00016461.JPG'
        return urlparse(request.url).path

settings.py

仍然需要IMAGES_STORE变量,并在ITEM_PIPELINES里加上自定义的管道

Python
1
2
3
4
5
IMAGES_STORE = './html/img'
ITEM_PIPELINES = {
    'scrapy.pipelines.images.ImagesPipeline': None,  # 关闭默认图片管道
    'gvo.pipelines.GvoImgPipeline': 1,  # 开启自定义图片管道,优先级为1最高
    }

自定义管道'gvo.pipelines.GvoImgPipeline'中,gvo是项目名,保存在scrapy.cfg,也是保存几个py文件的文件名。.pipelines固定不变,.GvoImgPipeline是piplines.py中自定义的类名。

scrapy设置

scrapy本什么没什么需要设置的。对于爬虫项目的设置,都在项目的settings.py文件里

settings.py

根据你的需要修改。

scrapy默认是深度优先(DFO)爬取,也就是优先获取全站的所有网页,再分析每个网页。这种情况适合网页数量少,抓取网页内容。缺点是如果网页太多,有可能极个别网页无法处理被忽略。如果是爬全站,应该改为广度优先,也就是获取到网页就先处理网页,再进一步爬其他网页。

下载器并发数量设置。CONCURRENT_REQUESTS是总并发数,下面两个分别是针对域名和IP的并发数。总并发数永远大于等于域名/ip并发数。

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 广度优先,队列类型为FIFO(first in first out)
DEPTH_PRIORITY = 1
SCHEDULER_DISK_QUEUE = 'scrapy.squeues.PickleFifoDiskQueue'
SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.FifoMemoryQueue'
 
# Configure maximum concurrent requests performed by Scrapy (default: 16)
# Scrapy 下载器将执行的并发(即同时)请求的最大数量
CONCURRENT_REQUESTS = 1000
 
# The download delay setting will honor only one of:
# CONCURRENT_REQUESTS_PER_DOMAIN = 16
# 将对任何单个域执行的并发(即同时)请求的最大数量
CONCURRENT_REQUESTS_PER_DOMAIN = 20
 
# CONCURRENT_REQUESTS_PER_IP = 16
# 将对任何单个 IP 执行的并发(即同时)请求的最大数量。
# 如果非零,则忽略CONCURRENT_REQUESTS_PER_DOMAIN,而使用该设置
CONCURRENT_REQUESTS_PER_IP = 0
本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可
标签: 暂无
最后更新:2022-04-24

wking

不管博客型博主

点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

目录
  • 准备工作
    • 用pycharm调试scrapy
    • 代码性能比较
      • response.xpath和beautifulsoup性能比较
      • BeautifulSoup.find_all()方法测试
      • BeautifulSoup的select_one()方法和find()方法性能比较
    • python使用多进程加快处理速度
  • 下载图片
    • 坑1 图片不下载
    • 坑2 gif图片下载后透明度、动态效果消失
    • 使用scrapy默认图片管道下载图片
      • items.py
      • 你的蜘蛛文件
      • settings.py
    • 使用自定义管道下载图片
      • piplines.py
      • settings.py
  • scrapy设置
    • settings.py
标签聚合
大航海时代 wordpress 一支红杏 C++ win10 linux R6300V2 OneNote

COPYRIGHT © 2024 wkings.blog. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang