几个月前,我完成了一次网络综合实验的课设,内容是要设计并实现一个网站下载程序。感觉里面有几个地方挺有意思的,于是在此记录下自己的思路,与大家分享。


实验要求

网站下载程序可以按照要求下载整个网站的网页,其原理是分析每个页面中的所有链接,然后根据该链接下载单个文件,并保存下来,采用递归方式进行扫描下载,直到下载页数达到设定好的最大值或者下载层数达到了设定的最大层数才停止。

主要功能

(1) 设定站点名称; (2) 设定最大下载页; (3) 设定最大下载层; (4) 设定是否下载多媒体文件(图片); (5) 设定是否下载其他站点网页; (6) 图形化显示。

思路分析

实现的思路并不难,首先获取用户输入的网址的 html 文件,然后分析其中的链接和图片并保存到一个列表,接下来对该列表中的链接继续重复这个过程。在下载过程中维持两个计数器,第一个计数器指示着已下载的网页数目,第二个计数器指示着当前下载到了第几层。

这不就是一个树的层次遍历嘛!每个链接相当于一个子树的根节点,图片就相当于一个叶子节点。

树的层次遍历需要队列来实现。首先将用户输入的网址入队,然后将其取出,并把该网页中的链接入队(相当于子节点入队),然后取出队头的第一个子节点,将该节点对应的网页中的链接继续入队(相当于把这个子节点的子节点入队)。这时队头的节点就是根节点的第二个子节点了。将其取出,继续进行之前的操作… …直到达到了两个计数器中的某个的要求。


接下来就是具体实现的方式了。因为要求图形化显示,所以需要写一个 GUI 界面,这里我在查阅了目前常用的 Python 图形界面库以后,最终选择了自带的 Tkinter 库。虽然之前没写过 Python 的界面,但是参照着官方文档的示例程序和网上的一些例子,也能比较容易地写出来。最终界面如下所示:

当点击开始下载的按钮以后,会触发判断方法,对用户的输入是否合法、网站是否能访问进行验证,如果验证无误才开始正式下载。这部分判断代码比较简单,就不放出来了,感兴趣的同学可以在这里查看源代码。

然后就是这个树的结构。每个节点都包含 value, children, layer, directory, number 一共五个属性,分别指的是对应的网址、子节点列表、所在的层次、下载后存放的目录,以及当前的序号。这里需要说明存放的目录,因为整个结构是一个树形结构,所以我将下载后的文件也以树形结构保存。定义树节点的具体代码如下:

class TreeNode(object):# 通过__slots__来限制成员变量__slots__ = ("value", "children", "layer", "directory", "number")# 构造函数def __init__(self, value, layer, directory, number):self.value = valueself.children = []self.layer = layerself.directory = directoryself.number = numberdef get_value(self):return self.valuedef get_layer(self):return self.layerdef get_directory(self):return self.directory# 返回节点的序号def get_number(self):return self.number# 插入子节点def insert_child(self, node):self.children.append(node)def pop_child(self):return self.children.pop(0)

接下来是核心代码部分,首先将根节点入队,然后通过while 循环来进行子节点的入队。之所以需要while,是因为可能根节点和它的页面中的子节点的数目之和还没有达到用户输入的数量,所以需要while 循环让子节点依次出队,并将这些子节点的子节点再次入队。还有一些其他需要注意的地方,在代码的注释进行了说明。

# 当队列不为空时一直循环,直到入队的节点数目达到用户输入的数量时return 退出
while treenode_queue:# 让队尾的节点出队,下载其 html 文件temp = treenode_queue.pop(0)re = requests.get(temp.get_value())# 注意需要进行编码,否则下载的 html 文件里的中文会乱码re.encoding = "utf-8"with open(os.path.join(temp.directory, str(temp.number)) + ".html", "w+", encoding="utf-8") as html_file:html_file.write(re.text)# 判断用户是否勾选下载多媒体文件,主要使用urllib 的urlretrieve()来下载if download_img:# 用SoupStrainer 类来过滤html 文件里的img 标签soup = BeautifulSoup(re.text, "html.parser", parse_only=SoupStrainer('img'))count = 1print("正在下载", temp.value, "的图片... ...")for img in soup:if not (img["src"] == ""):if str(img["src"][:2]) == r"//":img["src"] = "https:" + img["src"]img_dir = os.path.join(temp.directory, str(count))if img["src"][-3:] == "png":urllib.request.urlretrieve(img["src"], img_dir + ".png")elif img["src"][-3:] == "gif":urllib.request.urlretrieve(img["src"], img_dir + ".gif")elif img["src"][-3:] == "jpg":urllib.request.urlretrieve(img["src"], img_dir + ".jpg")else:  # 有些图片不带后缀,目前还无法下载下来,比如博客配图print("Failed :", img["src"])count = count + 1# 层数的判断出口,当下载的网页层数等于用户输入的层数时,退出if layer_count >= int(input_max_layers.get()) + 1:download_website_of_queue(*treenode_queue)with open(r"D:\Download_Website\README.txt", "w+") as readme_file:readme_file.write(get_dir_list(r"D:\Download_Website"))return# 接下来对于出队的节点使用 BeautifulSoup 和 SoupStrainer 来获取链接soup = BeautifulSoup(re.text, "html.parser", parse_only=SoupStrainer('a'))layer_count = layer_count + 1print("----------第" + str(layer_count) + "层----------")for each in soup:# 对于每个<a>标签中 href 的属性前四个字符是“http”的记录,首先输出其信息,然后构造节点将其入队,并且将其加入上方出队节点的 children 列表if each.has_attr("href") and each["href"][:4] == "http":website_count = website_count + 1print("第" + str(website_count) + "个网站/第" + str(layer_count) +"层:" + each["href"])anode = TreeNode(each["href"], layer_count, os.path.join(temp.directory, str(website_count)), website_count)temp.insert_child(anode)treenode_queue.append(anode)if website_count >= int(input_max_pages.get()):download_website_of_queue(*treenode_queue)# 发现下载数目够了的时候,调用函数下载队列中的所有节点的 html 文件,然后生成README文件,记录输出文件夹中的树形结构with open(r"D:\Download_Website\README.txt","w+") as readme_file:readme_file.write(get_dir_list(r"D:\Download_Website"))return

核心代码中有以下几个点需要注意:

  • 下载网页时需要进行编码,以避免中文乱码。这里统一编码成 utf-8。
  • 注意下载图片时的处理,有的图片网址前面只有 \\ ,没有 https 头,需要我们手动添加。
  • 注意函数出口的位置,先判断下载的层数是否已经达到,再进行下一步的操作。
  • 注意对两个计数器的操作和判断。

其中还有两个函数,分别是 download_website_of_queue(),以及 get_dir_list()。前者是用来下载队列中剩下的节点的,内容和核心代码部分比较像。后者用递归的方式产生一个 README 文件,这个文件以树形图的方式呈现了下载后的文件夹结构。这个方法的代码参考了这里。它们的代码如下:

def download_website_of_queue(*args):download_img = Falseif (int(btn_Group1.get()) == 1):download_img = True# 函数的输入是一个列表,对于队列中的每个节点,下载其html 文件for temp in args:re = requests.get(temp.get_value())re.encoding = "utf-8"if not os.path.exists(temp.directory):os.makedirs(temp.directory)with open(os.path.join(temp.directory, str(temp.number)) + ".html","w+",encoding="utf-8") as html_file:html_file.write(re.text)if download_img:# 以下为下载图片的代码,之前已经给出,此处不再赘述# 产生README 说明文件的函数,输入为一个文件夹目录的字符串,输出为该目录下所有文件的树形结构
# 以字符串的形式输出。主要是通过递归调用来实现。
BRANCH = '├─'
LAST_BRANCH = '└─'
TAB = '│  '
EMPTY_TAB = '   'def get_dir_list(path, placeholder=''):# 列出输入目录下的文件/文件夹,如果是文件夹,则加入folder_list 列表folder_list = [folder for folder in os.listdir(path) if os.path.isdir(os.path.join(path, folder))]# 列出输入目录下的文件/文件夹,如果是文件,则加入file_list 列表file_list = [file for file in os.listdir(path) if os.path.isfile(os.path.join(path, file))]result = ''# 对于文件夹列表中的第一个到倒数第二个元素,添加一个branch,然后递归调用直到# 最深的第一个子文件夹,里面只有文件,然后开始加入第一个到倒数第二个文件,# 直到添加完该文件夹的最后一个文件,依次类推,直到根目录文件夹的最深的最后# 一个子文件夹里的最后一个最后一个文件,递归才结束for folder in folder_list[:-1]:result += placeholder + BRANCH + folder + '\n'result += get_dir_list(os.path.join(path, folder), placeholder + TAB)if folder_list:result += placeholder + (BRANCH if file_list elseLAST_BRANCH) + folder_list[-1] + '\n'result += get_dir_list(os.path.join(path, folder_list[-1]),placeholder + (TAB if file_list else EMPTY_TAB))for file in file_list[:-1]:result += placeholder + BRANCH + file + '\n'if file_list:result += placeholder + LAST_BRANCH + file_list[-1] + '\n'return result

接下来用某博客网站进行测试:


主要内容差不多就是这些了。还有一点就是,程序界面上第二个按钮是用来打开对应的文件夹的,这个功能的实现是用 os 模块完成的,只需要一行代码:os.system((r"start explorer D:\Download_Website"))。本质上相当于是调用 Windows 控制台的指令来完成的。从这一点出发,也可以产生许多有意思的应用。

总的来说,这是一个很适合练手的小项目,整个代码加起来还不到 300 行,但可以考察到很多细节方面的东西 ~~~ 虽然我写出来了,但是里面的一些写法、命名之类的还是让人不怎么满意,可能还有一些隐藏的 bug …但之后我会抽空进行更进一步的完善的!

最后,给出这个小项目的源代码地址,欢迎交流和讨论 ~~~

用 Python 实现一个网页下载工具相关推荐

  1. python实战扫码下载_实例:用 Python 做一个扫码工具

    原标题:实例:用 Python 做一个扫码工具 来自公众号: 新建文件夹X 链接:https://blog.csdn.net/ZackSock/article/details/108610957Pyt ...

  2. 给Python漫画分集标题下载工具添加线程

    前情概要: 上一章,我们给这个python漫画分集标题下载工具添加了Qt界面,使用的是PySide6,大家看源码或许就可以看出来了,除了python语法不同之外,整个Qt库的使用和C++的使用几乎没什 ...

  3. python制作一个桌面小工具

    python实现一个桌面小工具,制作一个桌面的便签提醒工具 参考代码:https://github.com/cosven/memo 参考链接:https://www.jb51.net/article/ ...

  4. python 代码行数统计工具_使用Python设计一个代码统计工具

    问题 设计一个程序,用于统计一个项目中的代码行数,包括文件个数,代码行数,注释行数,空行行数.尽量设计灵活一点可以通过输入不同参数来统计不同语言的项目,例如: # type用于指定文件类型 pytho ...

  5. 用Python写一个纪念日计算工具

    用python写一个纪念日计算工具 前言 主要内容 日期计算函数 GUI布局 用pyinstaller打包生成.exe文件 打包后的.exe文件预览 运行结果 最后提前祝马英俊生日快乐吧~ 前言 纪念 ...

  6. 用Python做一个安全攻防工具:端口嗅探器(9)

    传送门 本系列原创博文传送门: 用Python做一个安全攻防工具:端口嗅探器(1) 用Python做一个安全攻防工具:端口嗅探器(2) 用Python做一个安全攻防工具:端口嗅探器(3) 用Pytho ...

  7. python批量下载文件只有1kb_详解如何用python实现一个简单下载器的服务端和客户端...

    话不多说,先看代码: 客户端: import socket def main(): #creat: download_client=socket.socket(socket.AF_INET,socke ...

  8. python数据预测_利用Python编写一个数据预测工具

    利用Python编写一个数据预测工具 发布时间:2020-11-07 17:12:20 来源:亿速云 阅读:96 这篇文章运用简单易懂的例子给大家介绍利用Python编写一个数据预测工具,内容非常详细 ...

  9. 怎么用python做一个解压缩小工具,以后再也不用下载各种格式的解压缩软件了...

    经常由于各种压缩格式的不一样用到文件的解压缩时就需要下载不同的解压缩工具去处理不同的文件,以至于桌面上的压缩工具就有三四种,于是使用python做了一个包含各种常见格式的文件解压缩的小工具. 阅读全文 ...

最新文章

  1. Memcache内存分配策略
  2. mysql 临时列_如何在MySQL中列出临时表列?
  3. 根据总用量计算每种包装规格的购买量和总价
  4. linux cron指定用户,Centos下crontab指定执行用户
  5. 79-定义不同颜色字体
  6. 给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
  7. Java Data Base Connection(JDBC)
  8. vue vuex 挂载_vue.js,javascript_Vuex的初始化失败,一直显示没有挂载到根组件上,奇怪了!,vue.js,javascript - phpStudy...
  9. eclipse java main方法传参数
  10. 程序员的系统桌面应该是什么样的
  11. lazarus 中文教程_Lazarus中文版下载|Pascal编译器Lazarus下载 v1.6.0中文版(附使用教程)_星星软件园...
  12. CDR案例:广告条幅banner设计
  13. netbeans java中文_NetBeans添加中文javadoc
  14. web前端页面优化详解
  15. k8s 超详细总结,面试必问
  16. NLP 中文分词-双向匹配算法(理论+Python实现)
  17. linux 显示bin 文件格式,bin文件扩展名,bin文件怎么打开?
  18. 雷电3接口 显卡 linux,炫龙 耀7000有雷电3接口么?
  19. Ubuntu 安装截图工具 Shutter
  20. 通过一个具体的例子,讲解 SAP BDC 技术的使用步骤

热门文章

  1. 【Django学习笔记 - 16】:DRF概述、Web应用模式(前后端分离简介)
  2. 手机游戏投屏软件有哪些 适合安卓用户
  3. Android bluetooth 蓝牙开发/蓝牙协议/蓝牙通信
  4. 快消品多租户SaaS加速企业运转
  5. 蓝桥杯C++组怒刷50道真题(填空题)
  6. uniapp 微信小程序阻止点击事件click冒泡
  7. STM32CubeMX芯片包下载失败
  8. 小程序自定义组件仿微信联系人可导航字母序排列数据列表
  9. 网络安全-渗透测试-Kali Linux教程篇 篇(六) 漏洞分析-02——AppScan-轻量级Web漏洞扫描、安全审计工具
  10. ByteArray各方法详解