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体积飞涨
  • debugconsole设置为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. 基于id3算法根据房价数据进行画图预测python

    根据已给的波士顿房价数据,对波斯顿房价进行预测.即,实现给出若干条件(如房间数.社区的低收入阶层的比率和镇上学生与教师数量比例的部分数据),要能说出给出的条件是否能够有效进行预测,如可以做有效预测,则 ...

  2. Windows servers 2008 环境下,域控DC和DNS,分离搭建过程。

    近来做有关于window服务器方面运维的实验,正好借此记录下来,便于日后回顾. 通常情况下,域控DC服务器和DNS服务器一般不在一起,所以需要将其分开建立.而这个时候两个服务器的建立有先后顺序,本文会 ...

  3. (几乎)完美实现 el-table 列宽自适应

    背景 Element UI 是 PC 端比较流行的 Vue.js UI 框架,它的组件库基本能满足大部分常见的业务需求.但有时候会有一些定制性比较高的需求,组件本身可能没办法满足.最近在项目里就碰到了 ...

  4. JVM关键字try、catch、finally、return执行过程

    关键字:jvm try catch finally return.指令 finally相当于在所有方法返回之前执行一次 finally中含有return其中finally中return会覆盖try和c ...

  5. linux 下分别使用pip2、pip3

    上次切换了Python2和Python3.但是Python3并没有pip,所有在Python3下不能安装包. 下面实现在Python3 下安装pip3 1,首先安装setuptools wget -- ...

  6. webpack打包初始入门教程

    Webpack 是一个前端资源加载/打包工具.它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源. 从图中我们可以看出,Webpack 可以将多种静态资源 js.css ...

  7. android开发 app闪退后fragment重叠bug解决方法,推荐使用第二种方法,完美解决问题

    解决方案为以下两种: 方法1:在fragmentActivity里oncreate方法判断savedInstanceState==null才生成新Fragment,否则不做处理. 方法2:在fragm ...

  8. Lua_C_C#

    lua调用c函数 https://www.cnblogs.com/etangyushan/p/4384368.html Lua中调用C函数 https://www.cnblogs.com/sifenk ...

  9. markdown 语法总结(一)

    1.标题 代码 注:# 后面保持空格 # h1 ## h2 ### h3 #### h4 ##### h5 ###### h6 ####### h7 // 错误代码 ######## h8 // 错误 ...

  10. Lua的Full UserData、Light UserData和metatable

    http://lua.2524044.n2.nabble.com/LightUserData-and-metatables-td3807698.html https://www.lua.org/man ...