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


实验要求

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

主要功能

(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_WebsiteREADME.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_WebsiteREADME.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

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

存放下载结果的文件夹
README 文件和 html 文件

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

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

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

bootstracp实现树形列表_用 Python 实现一个网页下载工具相关推荐

  1. 用 Python 实现一个网页下载工具

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

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

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

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

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

  4. 如何用python写小工具_用python写一个录音小工具

    Python的paramiko,wxPython库的应用 Sound eXchange 命令行 需求 最近在给一个做语音识别的项目做QA工作.众所周知,此类人工智能方面的项目都需要一些数据收集的工作. ...

  5. python小工具开发_使用Python制作一个桌面小工具

    今天,我们制作一个有意思的小工具,利用Python获取历史上的今天发生的事情. [分析] 个人总结了一下,"历史上的今天"有很多调用接口.有很多调用接口都是收费的或者就是免费但限制 ...

  6. python生成多个随机数列表_在python中生成1到6之间的6个随机数的列表

    使用 list comprehension: import random def startTheGame(): mylist=[random.randint(1, 6) for _ in range ...

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

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

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

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

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

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

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

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

最新文章

  1. 两条线段相切弧_两条直线间的圆弧连接
  2. mysql获取两个表中日期字段的最小差值
  3. ccf权限查询java_201612-3 ccf 权限查询
  4. 会计科目****不能使用(请更正)
  5. 如何快速掌握一门新技术/语言/框架…
  6. IDC:全球经济危机为云计算带来曙光
  7. 【链接保存】十分钟上手sklearn:安装,获取数据,数据预处理
  8. 二分法解决力扣374.猜数字大小 C语言
  9. pwd赋值给变量 shell_Shell脚本中的变量详解
  10. http session 基础知识
  11. Centos给/根分区扩容(图文讲解)
  12. Linux多线程编程之pthread
  13. 新主播如何在直播行业混得好
  14. iPhone手机屏幕尺寸
  15. 一个简单的吃豆子游戏
  16. Python如何设置对数log坐标系的range
  17. 1688API item_search_img - 按图搜索1688商品(拍立淘)
  18. 智能车八邻域图像算法
  19. Wordpress站点使用七牛云对象储存以及CDN加速
  20. div浮动到另一个div上面或者浮动到img图片上面

热门文章

  1. keras读取训练好的模型参数并把参数赋值给其它模型
  2. InnoDB在MySQL默认隔离级别下解决幻读
  3. layer子窗口与父窗口传值
  4. Redis 3.2.4编译安装
  5. 14.6.4 Configuring the Memory Allocator for InnoDB 配置InnoDB 内存分配器
  6. Android面试题整理【转载】
  7. 知识图谱中的结构信息建模
  8. 树分类、线性回归和树回归的感性认知
  9. python找不同数字的个数_在Python中找到N个按位或等于K的不同数字
  10. 麻省计算机音乐博士,MIT又一突破!用AI过滤音源,让音乐更悦耳