主頁 > 後端開發 > 基于Scrapy的互動式漫畫爬蟲

基于Scrapy的互動式漫畫爬蟲

2020-09-29 19:57:58 後端開發

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方面還只算用到了皮毛

歡迎大家前往 本專案 試用下交流下意見

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/139685.html

標籤:Python

上一篇:帶你了解Python網路爬蟲四大選擇器用法原理!

下一篇:帶你了解Python網路爬蟲四大選擇器用法原理!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more