基于Python3制作一个带GUI界面的小说爬虫工具
作者:mortimer 发布时间:2023-04-02 17:33:04
标签:Python3,小说,爬虫
效果图
最近帮朋友写个简单爬虫,顺便整理了下,搞成了一个带GUI界面的小说爬虫工具,用来从笔趣阁爬取小说。
开发完成后的界面
采集过程界面
采集后存储
主要功能
1.多线程采集,一个线程采集一本小说
2.支持使用代理,尤其是多线程采集时,不使用代理可能封ip
3.实时输出采集结果
使用 threading.BoundedSemaphore() pool_sema.acquire() pool_sema.release()
来限制线程数量,防止并发线程过。具体限制数量,可在软件界面输入,默认5个线程
# 所有线程任务开始前
pool_sema.threading.BoundedSemaphore(5)
# 具体每个线程开始前 锁
pool_sema.acquire()
....
# 线程任务执行结束释放
pol_sema.release()
用到的第三方模块
pip install requests
pip install pysimplegui
pip install lxml
pip install pyinstaller
GUI 界面使用了一个tkinter 的封装库 PySimpleGUI
, 使用非常方便,虽然界面不够漂亮,但胜在简单,非常适合开发些小工具。https://pysimplegui.readthedocs.io/en/latest/比如这个界面的布局,只需简单几个 list
layout = [
[sg.Text('输入要爬取的小说网址,点此打开笔趣阁站点复制', font=("微软雅黑", 12),
key="openwebsite", enable_events=True, tooltip="点击在浏览器中打开")],
[sg.Text("小说目录页url,一行一个:")],
[
sg.Multiline('', key="url", size=(120, 6), autoscroll=True, expand_x=True, right_click_menu=['&Right', ['粘贴']]
)
],
[sg.Text(visible=False, text_color="#ff0000", key="error")],
[
sg.Button(button_text='开始采集', key="start", size=(20, 1)),
sg.Button(button_text='打开下载目录', key="opendir",
size=(20, 1), button_color="#999999")
],
[sg.Text('填写ip代理,有密码格式 用户名:密码@ip:端口,无密码格式 ip:端口。如 demo:123456@123.1.2.8:8580')],
[
sg.Input('', key="proxy"),
sg.Text('线程数量:'),
sg.Input('5', key="threadnum"),
],
[
sg.Multiline('等待采集', key="res", disabled=True, border_width=0, background_color="#ffffff", size=(
120, 6), no_scrollbar=False, autoscroll=True, expand_x=True, expand_y=True, font=("宋体", 10), text_color="#999999")
],
]
打包为 exe 命令
pyinstaller -Fw start.py
全部源码
import time
import requests
import os
import sys
import re
import random
from lxml import etree
import webbrowser
import PySimpleGUI as sg
import threading
# user-agent
header = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36"
}
# 代理
proxies = {}
# 删除书名 * 殊符号
# 笔趣阁基地址
baseurl = 'https://www.xbiquwx.la/'
# 线程数量
threadNum = 6
pool_sema = None
THREAD_EVENT = '-THREAD-'
cjstatus = False
# txt存储目录
filePath = os.path.abspath(os.path.join(os.getcwd(), 'txt'))
if not os.path.exists(filePath):
os.mkdir(filePath)
# 删除特殊字符
def deletetag(text):
return re.sub(r'[\[\]#\/\\:*\,;\?\"\'<>\|\(\)《》&\^!~=%\{\}@!:。·!¥……() ]','',text)
# 入口
def main():
global cjstatus, proxies, threadNum, pool_sema
sg.theme("reddit")
layout = [
[sg.Text('输入要爬取的小说网址,点此打开笔趣阁站点复制', font=("微软雅黑", 12),
key="openwebsite", enable_events=True, tooltip="点击在浏览器中打开")],
[sg.Text("小说目录页url,一行一个:")],
[
sg.Multiline('', key="url", size=(120, 6), autoscroll=True, expand_x=True, right_click_menu=['&Right', ['粘贴']]
)
],
[sg.Text(visible=False, text_color="#ff0000", key="error")],
[
sg.Button(button_text='开始采集', key="start", size=(20, 1)),
sg.Button(button_text='打开下载目录', key="opendir",
size=(20, 1), button_color="#999999")
],
[sg.Text('填写ip代理,有密码格式 用户名:密码@ip:端口,无密码格式 ip:端口。如 demo:123456@123.1.2.8:8580')],
[
sg.Input('', key="proxy"),
sg.Text('线程数量:'),
sg.Input('5', key="threadnum"),
],
[
sg.Multiline('等待采集', key="res", disabled=True, border_width=0, background_color="#ffffff", size=(
120, 6), no_scrollbar=False, autoscroll=True, expand_x=True, expand_y=True, font=("宋体", 10), text_color="#999999")
],
]
window = sg.Window('采集笔趣阁小说', layout, size=(800, 500), resizable=True,)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED or event == 'close': # if user closes window or clicks cancel
break
if event == "openwebsite":
webbrowser.open('%s' % baseurl)
elif event == 'opendir':
os.system('start explorer ' + filePath)
elif event == 'start':
if cjstatus:
cjstatus = False
window['start'].update('已停止...点击重新开始')
continue
window['error'].update("", visible=False)
urls = values['url'].strip().split("\n")
lenth = len(urls)
for k, url in enumerate(urls):
if (not re.match(r'%s\d+_\d+/' % baseurl, url.strip())):
if len(url.strip()) > 0:
window['error'].update("地址错误:%s" % url, visible=True)
del urls[k]
if len(urls) < 1:
window['error'].update(
"每行地址需符合 %s84_84370/ 形式" % baseurlr, visible=True)
continue
# 代理
if len(values['proxy']) > 8:
proxies = {
"http": "http://%s" % values['proxy'],
"https": "http://%s" % values['proxy']
}
# 线程数量
if values['threadnum'] and int(values['threadnum']) > 0:
threadNum = int(values['threadnum'])
pool_sema = threading.BoundedSemaphore(threadNum)
cjstatus = True
window['start'].update('采集中...点击停止')
window['res'].update('开始采集')
for url in urls:
threading.Thread(target=downloadbybook, args=(
url.strip(), window,), daemon=True).start()
elif event == "粘贴":
window['url'].update(sg.clipboard_get())
print("event", event)
if event == THREAD_EVENT:
strtext = values[THREAD_EVENT][1]
window['res'].update(window['res'].get()+"\n"+strtext)
cjstatus = False
window.close()
#下载
def downloadbybook(page_url, window):
try:
bookpage = requests.get(url=page_url, headers=header, proxies=proxies)
except Exception as e:
window.write_event_value(
'-THREAD-', (threading.current_thread().name, '\n请求 %s 错误,原因:%s' % (page_url, e)))
return
if not cjstatus:
return
# 锁线程
pool_sema.acquire()
if bookpage.status_code != 200:
window.write_event_value(
'-THREAD-', (threading.current_thread().name, '\n请求%s错误,原因:%s' % (page_url, page.reason)))
return
bookpage.encoding = 'utf-8'
page_tree = etree.HTML(bookpage.text)
bookname = page_tree.xpath('//div[@id="info"]/h1/text()')[0]
bookfilename = filePath + '/' + deletetag(bookname)+'.txt'
zj_list = page_tree.xpath(
'//div[@class="box_con"]/div[@id="list"]/dl/dd')
for _ in zj_list:
if not cjstatus:
break
zjurl = page_url + _.xpath('./a/@href')[0]
zjname = _.xpath('./a/@title')[0]
try:
zjpage = requests.get(
zjurl, headers=header, proxies=proxies)
except Exception as e:
window.write_event_value('-THREAD-', (threading.current_thread(
).name, '\n请求%s:%s错误,原因:%s' % (zjname, zjurl, zjpage.reason)))
continue
if zjpage.status_code != 200:
window.write_event_value('-THREAD-', (threading.current_thread(
).name, '\n请求%s:%s错误,原因:%s' % (zjname, zjurl, zjpage.reason)))
return
zjpage.encoding = 'utf-8'
zjpage_content = etree.HTML(zjpage.text).xpath('//div[@id="content"]/text()')
content = "\n【"+zjname+"】\n"
for _ in zjpage_content:
content += _.strip() + '\n'
with open(bookfilename, 'a+', encoding='utf-8') as fs:
fs.write(content)
window.write_event_value(
'-THREAD-', (threading.current_thread().name, '\n%s:%s 采集成功' % (bookname, zjname)))
time.sleep(random.uniform(0.05, 0.2))
# 下载完毕
window.write_event_value('-THREAD-', (threading.current_thread(
).name, '\n请求 %s 结束' % page_url))
pool_sema.release()
if __name__ == '__main__':
main()
来源:https://juejin.cn/post/7061603255616274462
0
投稿
猜你喜欢
- 在上一篇文章中,我介绍了MySQL对XML支持的部分功能,包括--xml命令行选项,以及MySQL 5.1.5中开始引入的新功能。今天我将介
- 我们平常用 IE 打开一个普通的本地 xml 文件,其形式通常都是如下图: 默认样式看得多了就不觉得有什么特别。但对于少接触 xml 的人来
- 大家觉得在接手遗留代码时,见到什么东东是最让人感到不耐烦的?复杂无比的 UML ?我觉得不是。我的答案是,超过两个 else 的 if ,或
- 最近在研究网页的切片算法,很可能很多人不知道什么是切片算法,其实这是一种面向搜索引擎的网页分块、切片的原理,目前随着工作的深入,逐渐碰到了各
- 一、php中pcntl_fork函数概述pcntl_fork()函数是php中用于创建子进程的一个函数,返回创建的子进程的pid。该函数创建
- javascript sort()排序用法sort() 方法用于对数组的元素进行排序,并返回数组。默认排序顺序是根据字符串UniCode码。
- 之前一篇文章里提到了利用Cython来编译Python,这次来讲一下如何用Cython给Python写扩展库。两种语言混合编程,其中最重要的
- 本文实例为大家分享了js实现瀑布流效果的具体代码,供大家参考,具体内容如下CSS样式:<style> .cont{margin:
- 在第一部分和第二部分中我们分别介绍了改善网站性能中页面内容和服务器的几条守则,除此之外,JavaScript和CSS也是我们页
- 问题:pycharm无法调用pip安装的包原因:pycharm没有设置解析器解决方法:打开pycharm->File->Sett
- 目前防采集的方法有很多种,先介绍一下常见防采集策略方法和它的弊端及采集对策: 一、判断一个IP在一定时间内对本站页面的访问次数,如果明显超过
- 项目地址:https://github.com/PythonerKK/django-generate-pdf/tree/master这个de
- 本文实例讲述了Python爬虫实现爬取百度百科词条功能。分享给大家供大家参考,具体如下:爬虫是一个自动提取网页的程序,它为搜索引擎从万维网上
- Django将秒转换为xx天xx时xx分,具体代码如下所示:from django.utils.translation import nge
- 光学元件类平面反射镜是一种极为简单的模型,因为我们只需要考虑一个平面即可。但是除此之外的其他光学元件,可能会变得有些复杂:我们必须考虑光在入
- 文末附完整源代码实现过程...想实现这样一个功能,然后pyqt5中又没有现成的组件可以使用,于是就想着只能通过绘图的方式来实现。说到绘图的话
- 一.axisaxis就是指定轴。三维数组可看作元素是二维数组的一维数组,二维数组可看作元素是一维数组的一维数组 。(这么理解就舒服了!)例:
- 模板引擎的思想是来源于MVC(Model View Controller)模型,即模型层、视图层、控制器层。在Web端,模型层为数据库的操作
- Pygame的mixer 模块可以依据命令播放一个或多个声音,并且也可以将这些声音混合在一起。而获得声音需要四个步骤:一、启动mixer进程
- if(document.mylist.length != "undefined" ) {} 这个用法有误. 正确的是 i