Github项目地址

前言

该项目始于个人兴趣,本意为给无代码经验的朋友做到能开箱即用

阅读此文需要少量Scrapy,PyQt 知识,全文仅分享交流 摘要思路,如需可阅读源码,欢迎提 issue

一、Scrapy

思路构想

基类封装了框架所需方法,框架基于三级页面 (标题-章节-详情页) 网站,内部方法分岔线基于交互思想

  1. GUI传参并开启后台 >> spider开始工作于重写的start_requests >> 在parse等处理resp的方法后挂起等待选择
  2. 执行顺序为 (1) parse -- frame_book --> (2) parse_section -- frame_section -->(3) yield item frame方法下述讲解
  3. pipeline对item作最后的下载,改名等处理,至此spider完成一个生命周期,发送结束信号逻辑交回GUI

BaseClassSpider

class BaseComicSpider(scrapy.Spider):
"""改写start_requests"""
step = 'loop'
current_status = {}
print_Q = None
current_Q = None
step_Q = None
bar = None # 此处及以上变量均为交互信号
total = 0 # item 计数,pipeline处讲解
search_url_head = NotImplementedError('需要自定义搜索网址')
mappings = {'': ''} # mappings自定义关键字对应网址
# ……………………
def parse(self, response):
frame_book_results = self.frame_book(response)
yield scrapy.Request(url=title_url, ………………) def frame_book(self, response) -> dict:
raise NotImplementedError def elect_res(self, elect: list, frame_results: dict, **kw) -> list:
# 封装方法实现(1)选择elect与(2)frame方法格式化后的显示result ->
# -> 返回[[elected_title1, title1_url], [title2, title2_url]……]的格式数据
pass
# ……………………
def close(self, reason):
# ………处理管道,session等关闭工作
self.print_Q.put('结束信号')

InstanceClassSpider

后台执行的实例,简单的二级页面仅需复写两个frame方法,对应的是扩展的基类2

frame方法功能为定位目标元素位置,实时清洗数据返回给前端显示

class ComicxxxSpider(BaseComicSpider2):
name = 'comicxxx'
allowed_domains = ['m.xxx.com']
search_url_head = 'http://m.xxx.com/search/?keywords='
mappings = {'更新': 'http://m.xxx.com/update/', '排名': 'http://m.xxx.com/rank/'}
def frame_book(self, response):
# ……………………
title = target.xpath(title_xpath).get().strip()
self.print_Q.put(example_b.format(str(x + 1), title)) # 发送前端print信号
def frame_section(self, response):
pass # 类上

setting

setting.py自定义部分与部署相关,使用 工具集 的方法读取配置文件构成变量

IMAGES_STORE, log_path, PROXY_CUST, LOG_LEVEL = get_info()
os.makedirs(f'{log_path}', exist_ok=True)
# 日志输出
LOG_FILE = f"{log_path}/scrapy.log"
SPECIAL = ['joyhentai']

pipelines

def file_path(self, request, response=None, info=None):
"""图片下载存储前调用,默认为url的md5后字符串,此处修改成自定义的有序命名"""
title = sub(r'([|.:<>?*"\\/])', '-', request.item.get('title')) # 对非法字符预处理
section = sub(r'([|.:<>?*"\\/])', '-', request.item.get('section'))
page = '第%s页.jpg' % request.item.get('page')
spider = self.spiderinfo.spider # setting.py的参数在此使用
basepath = spider.settings.get('IMAGES_STORE')
path = f"{basepath}\\特殊\\{title}" if spider.name in spider.settings.get(
'SPECIAL') else f"{basepath}\\{title}\\{section}\\"
os.makedirs(path, exist_ok=True)
return os.path.join(path, page) def image_downloaded(self, response, request, info):
"""继承的ImagesPipeline图片(文件)下载完成方法,下载进度条动态显示的实现就在此处"""
self.now += 1 # (ComicPipeline)self.now即为现时处理量
spider = self.spiderinfo.spider
percent = int((self.now / spider.total) * 100) # spider.total即为item的总任务量
if percent > self.threshold:
percent -= int((percent / self.threshold) * 100) # 进度缓慢化(算法待优化)
spider.bar.put(int(percent)) # 后台打印百分比进度扔回GUI界面
super(ComicPipeline, self).image_downloaded(response=response,request=request, info=info)

其他:Items与Middlewares要点不多,略过


二、GUI (Qt)

主界面及功能

  • 按键逻辑:槽函数实现,内部实现一定量的按钮禁用方法引导操作

    • 选取网站 按钮与 输入关键字 构成参数,由→搜索 按钮触发工作线程等生成,然后替换成 Next 按钮
    • Next 按钮为正常流程 -- 触发解除后台因等待输入造成的主动阻塞 同时传递 输入序号 的值
    • Retry 按钮承担后台Spider中parse方法间的逆跳转,以及重启GUI的功能
  • 视窗与信息

    • 主视窗textbrowser,流式显示;整体内联其他视窗,略过

    • 说明按钮通用说明、底下状态栏通过setStatusTip方法于各操作时提供人性化操作提示

    • 进度条,关联 pipeline 的信号输出

节选 Next 按钮逻辑的 槽函数

def next_schedule(self):
def start_and_search():
self.log.debug('===--→ -*- searching')
self.next_btn.setText('Next')
keyword = self.searchinput.text()[6:].strip()
index = self.chooseBox.currentIndex() if self.nextclickCnt == 0: # 从section步 回parse步 的话以免重开
self.bThread = WorkThread(self)
def crawl_btn(text):
if len(text) > 5:
self.crawl_btn.setEnabled(self.step_recv()=='parse section')
self.next_btn.setDisabled(self.crawl_btn.isEnabled())
self.chooseinput.textChanged.connect(crawl_btn) self.p = Process(target=crawl_what, args=(index, self.print_Q, self.bar, self.current_Q, self.step_Q))
self.bThread.print_signal.connect(self.textbrowser_load)
self.bThread.item_count_signal.connect(self.processbar_load)
self.bThread.finishSignal.connect(self.crawl_end)
self.p.start()
self.bThread.start()
self.log.info(f'-*-*- Background thread starting') self.chooseBox.setDisabled(True)
self.params_send({'keyword':keyword})
self.log.debug(f'website_index:[{index}], keyword [{keyword}] success ') def _next():
self.log.debug('===--→ nexting')
self.judge_retry() # 非retry的时候先把retry=Flase解锁spider的下一步
choose = judge_input(self.chooseinput.text()[5:].strip())
if self.nextclickCnt == 1:
self.book_choose = choose
# 选0的话这里要爬虫返回书本数量数据
self.book_num = len(self.book_choose)
if self.book_num > 1:
self.log.info('book_num > 1')
self.textBrowser.append(self.warning_(f'警告!!多选书本时不要随意使用 retry<br>'))
self.chooseinput.clear()
# choose逻辑 交由crawl, next,retry3个btn的schedule控制
self.params_send({'choose': choose})
self.log.debug(f'send choose: {choose} success') self.retrybtn.setEnabled(True)
if self.next_btn.text()!='搜索':
_next()
else:
start_and_search() self.nextclickCnt += 1
self.searchinput.setEnabled(False)
self.chooseinput.setFocusPolicy(Qt.StrongFocus)
self.step_recv() # 封装的self.step_Q处理方法
self.log.debug(f"===--→ next_schedule end (now step: {self.step})\n")

后台线程

后台爬虫进程创建方法 ,上述UI主线程中Next逻辑的 start_and_search() 调用

def crawl_what(index, print_Q, bar, current_Q, step_Q):
spider_what = {1: 'comic1,
2: 'comic2',
3: 'comic3'}
freeze_support()
process = CrawlerProcess(get_project_settings())
process.crawl(spider_what[index], print_Q=print_Q, bar=bar, current_Q=current_Q, step_Q=step_Q)
process.start()
process.join()
process.stop()

分离UI主线程与工作线程(项目代码中此处可整合爬虫进程一起)

class WorkThread(QThread):
item_count_signal = pyqtSignal(int)
print_signal = pyqtSignal(str)
finishSignal = pyqtSignal(str)
active = True def __init__(self, gui):
super(WorkThread, self).__init__()
self.gui = gui def run(self):
while self.active:
self.msleep(8)
if not self.gui.print_Q.empty():
self.msleep(8)
self.print_signal.emit(str(self.gui.print_Q.get()))
if not self.gui.bar.empty():
self.item_count_signal.emit(self.gui.bar.get())
self.msleep(10)
if '完成任务' in self.gui.textBrowser.toPlainText():
self.item_count_signal.emit(100)
self.msleep(20)
break
if self.active:
from ComicSpider.settings import IMAGES_STORE
self.finishSignal.emit(IMAGES_STORE)

辅助工具

资源处理工具

  • PYUIC >>> 将.ui界面文件转换成py文件
  • pyrcc5 >>> 将编入资源路径后的qrc文件,转换成py文件

工具集 utils.py

def get_info():
with open(f'./setting.txt', 'r', encoding='utf-8') as fp:
text = fp.read()
sv_path = re.findall(r'<([\s\S]*)>', text)[0]
level = re.findall('(DEBUG|INFO|ERROR)', text)[0]
# ……………… def cLog(name, level='INFO', **kw) -> Logger:
# 同理读取setting.txt,
level = re.search('(DEBUG|WARNING|ERROR)', text).group(1) def judge_input(_input: str) -> list:
"""
"6" return [6] // "1+3+5" return [1,3,5]
"4-6" return [4,5,6] // "1+4-6" return [1,4,5,6]
"""

三、部署

部署实为pyinstaller打包成exe

pyinstaller注意要点:

  • 查阅资料和前人摸路,scrapy的打包需要在主运行文件中导入大量模块,可参考 我的配置
  • spec的datas中每个值前为项目现位置,后为运行时位置;慎用网上传授的('.', '.'),使用不当会使得git体积飞涨
  • 将debug、console设置为True,方便调试 ( 与上述导入模块调试有所关联

spec参考

# -*- mode: python -*-
block_cipher = None
a = Analysis(['crawl_go.py'],
pathex=['D:\\xxxxxxxxxxxxxxxx\\ComicSpider'],
binaries=[],
datas=[('D:\python\Lib\site-packages\scrapy\mime.types','scrapy'),
('D:\python\Lib\site-packages\scrapy\VERSION','scrapy'),
('./ComicSpider','ComicSpider'), ('./GUI', 'GUI'),
('./gui.py', '.'), ('./material_ct.py', '.'), ('./utils.py', '.'),
], # -*-
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='ComicSpider',
debug=True, # -*-
bootloader_ignore_signals=False,
strip=False,
upx=True,
runtime_tmpdir=None,
console=True, icon='exe.ico') # -*-

打包后目录树

├── ComicSpider.exe
├── log
│   ├── GUI.log
│   └── scrapy.log
├── scrapy.cfg # 经测试过,scrapy.cfg内置到exe中并不起作用,猜测与缓存路径有关,外置无伤大雅
├── setting.txt

总结

scrapy用在这种单机交互上的效果不错,pyqt方面还只算用到了皮毛

欢迎大家前往 本项目 试用下交流下意见

基于Scrapy的交互式漫画爬虫的更多相关文章

  1. 基于Scrapy的B站爬虫

    基于Scrapy的B站爬虫 最近又被叫去做爬虫了,不得不拾起两年前搞的东西. 说起来那时也是突发奇想,想到做一个B站的爬虫,然后用的都是最基本的Python的各种库. 不过确实,实现起来还是有点麻烦的 ...

  2. 基于scrapy框架的分布式爬虫

    分布式 概念:可以使用多台电脑组件一个分布式机群,让其执行同一组程序,对同一组网络资源进行联合爬取. 原生的scrapy是无法实现分布式 调度器无法被共享 管道无法被共享 基于 scrapy+redi ...

  3. 一个基于Scrapy框架的pixiv爬虫

    源码 https://github.com/vicety/Pixiv-Crawler,功能什么的都在这里介绍了 说几个重要的部分吧 登录部分 困扰我最久的部分,网上找的其他pixiv爬虫的登录方式大多 ...

  4. python基于scrapy框架的反爬虫机制破解之User-Agent伪装

    user agent是指用户代理,简称 UA. 作用:使服务器能够识别客户使用的操作系统及版本.CPU 类型.浏览器及版本.浏览器渲染引擎.浏览器语言.浏览器插件等. 网站常常通过判断 UA 来给不同 ...

  5. 爬虫学习之基于Scrapy的爬虫自动登录

    ###概述 在前面两篇(爬虫学习之基于Scrapy的网络爬虫和爬虫学习之简单的网络爬虫)文章中我们通过两个实际的案例,采用不同的方式进行了内容提取.我们对网络爬虫有了一个比较初级的认识,只要发起请求获 ...

  6. 基于Python,scrapy,redis的分布式爬虫实现框架

    原文  http://www.xgezhang.com/python_scrapy_redis_crawler.html 爬虫技术,无论是在学术领域,还是在工程领域,都扮演者非常重要的角色.相比于其他 ...

  7. 基于scrapy爬虫的天气数据采集(python)

    基于scrapy爬虫的天气数据采集(python) 一.实验介绍 1.1. 知识点 本节实验中将学习和实践以下知识点: Python基本语法 Scrapy框架 爬虫的概念 二.实验效果 三.项目实战 ...

  8. 基于Scrapy框架的Python新闻爬虫

    概述 该项目是基于Scrapy框架的Python新闻爬虫,能够爬取网易,搜狐,凤凰和澎湃网站上的新闻,将标题,内容,评论,时间等内容整理并保存到本地 详细 代码下载:http://www.demoda ...

  9. Scrapy框架之基于RedisSpider实现的分布式爬虫

    需求:爬取的是基于文字的网易新闻数据(国内.国际.军事.航空). 基于Scrapy框架代码实现数据爬取后,再将当前项目修改为基于RedisSpider的分布式爬虫形式. 一.基于Scrapy框架数据爬 ...

随机推荐

  1. 配置tomacat外部应用

    原来我们的项目一般放置在tomcat文件夹的webapp下,现在我放到外面,也希望tomact可以找到他

  2. Event loop详解(包含Node端)

    Event loop事件循环,是一个执行模型.不同的浏览器以及Nodejs里的具体实现是不一样的. 一,浏览器端: HTML5规范里有明确定义,简单的说: 1,JS是单线程的,执行的时候在一条主栈上: ...

  3. python实用小技能分享,教你如何使用 Python 将 pdf 文档进行 加密 解密

    上次说了怎么将word转换为pdf格式 及 实现批量将word转换为pdf格式(点击这里),这次我又get到一个新技能–使用 Python 将 pdf 文档进行 加密 解密,哈哈哈 希望帮到更多人! ...

  4. 【转】Tomcat搭建文件服务器

    http://blog.csdn.net/yin_jw/article/details/43524659 1. 配置在tomcat目录下 直接把文件放在 tomcat6/webapps/ROOT 目录 ...

  5. unity physics相关

    1. With careful positioning and sizing, compound colliders can often approximate the shape of an obj ...

  6. 常用API【2】

    常用API 1.Math类 1.1 Math类概述 Math包含执行基本数字运算的方法 没有构造方法,如何使用类中的成员呢? 看类的成员是否是静态的,如果是,可以通过类名直接调用 1.2Math类的常 ...

  7. C007:输入美元数量,用最少的20美元,10美元,5美元和1美元付款

    代码: #include "stdafx.h" int _tmain(int argc, _TCHAR* argv[]) { int amount; do{ printf(&quo ...

  8. 用于测试 JsonAnalyzer2 1.01版的测试用例

    14. 原文={"animal":"ca,t","color":"ora:nge","isMale" ...

  9. leetcode刷题-86分隔链表

    题目 给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前. 你应当保留两个分区中每个节点的初始相对位置. 示例: 输入: head = 1->4 ...

  10. 深入理解Java中的装箱与拆箱

    一.Java数据类型 1.在说装箱与拆箱之前,先说一下Java的基本数据类型,Java从数据类型上可以划分为值类型与引用类型,值类型是四类八种,分别是: 整数型:byte̵,short̵,int̵,l ...