分享一个自己写的网站下载器,编程语言是 Python。这个网站下载器主要下载网站可访问的静态资源,即各种静态文件,包括html、js、css、jpg、png、gif、mp3、mp4、pdf、doc、xls等等等等,具体可参考程序内容。本下载器默认开启8个线程,对下载的网站文件按照原本组织方式存储,保存在当前运行程序的文件夹下。下载的网站和线上的网站看起来是一模一样的,这样,下次没网的时候,你也可以查看了。

项目保存在Github上,链接是https://github.com/LiebeU/WebSite-Downloader。如果这个网站下载器对你有帮助的话,欢迎star我一下。

怎么用呢?复制代码并保存为.py文件。程序最下方有示例。实例化Manager类,并传给它一个链接,这个链接就是你要下载的网站的链接,然后调用这个实例的start()方法,然后是,waiting......

对了,里面的链接,那个网站请不要去爬它哦,占用别人的带宽是很不好的习惯。只是用来作为一个示例写上去的,而且,我不是那个学校的学生,哈。

"""
网站下载器
"""
__author__ = 'Stardust1001'from urllib import request, error
from urllib.request import Request, urlopen, urljoin, urlretrieve, urlparse
import os, shutil, re, time, threading, http
from http import cookiejar
from queue import Queue, Empty
import loggingimport socketsocket.setdefaulttimeout(20)import ssl
try:_create_unverified_https_context = ssl._create_unverified_context
except AttributeError:pass
else:ssl._create_default_https_context = _create_unverified_https_contextdef init_opener():cookie = cookiejar.CookieJar()cookie_support = request.HTTPCookieProcessor(cookie)return request.build_opener(cookie_support)opener = init_opener()def init_logger():logger = logging.getLogger()logger.setLevel(logging.INFO)console_handler = logging.StreamHandler()console_handler.setLevel(logging.INFO)file_handler = logging.FileHandler('log.log', mode='w', encoding='UTF-8')file_handler.setLevel(logging.NOTSET)formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')console_handler.setFormatter(formatter)file_handler.setFormatter(formatter)logger.addHandler(console_handler)logger.addHandler(file_handler)return loggerlogger = init_logger()class Manager:"""爬虫主线程的管理器从子线程里获取新的链接,处理后添加进要爬取的链接 Queue 队列子线程从主线程提供的链接 Queue 队列获取链接进行爬取"""def __init__(self, home_url):# 爬取网站域名的各个子域名# 下载的网站的根文件夹,网站可能有不同子域名,提供一个更高级的文件夹路径 -sitehome_dir = '{0}-site/{1}'.format(home_url.split('.')[1], home_url.split('/')[2])# home_dir = '/Users/liebeu/Desktop/localhost-site/localhost'if os.path.exists(home_dir):shutil.rmtree(os.path.dirname(home_dir))os.makedirs(home_dir)parsed_url = urlparse(home_url)scheme = parsed_url.scheme# 爬取的网站的顶级域名top_domain = '.'.join(parsed_url.netloc.split('.')[1:])# 每个请求最大尝试次数max_tries = 3# 要爬取的链接 Queue 队列self.link_queue = Queue()self.link_queue.put(home_url)# 链接 set ,对新连接进行唯一性判断,然后添加进 Queue 队列self.links = set([home_url])# 子线程爬虫列表self.spiders = []# 默认开启 8 个子线程for i in range(8):self.spiders.append(Spider(home_dir, home_url, self.link_queue, scheme, top_domain, max_tries))def start(self):"""开启主线程的爬虫管理器"""for spider in self.spiders:spider.start()# 上次有新链接的时间,默认延时 60 秒,超过时间就结束程序last_new_time = time.time()# 从子线程获取新链接,添加进 Queue 队列while True:for spider in self.spiders:new_links = spider.get_links()if new_links:last_new_time = time.time()for link in new_links:if not link in self.links and len(link) < 250:sharp_index = link.find('#')if sharp_index > 0:link = link[0:sharp_index]self.links.add(link)self.link_queue.put(link, True)if time.time() - last_new_time >= 60:break# 响铃提醒下载完成for i in range(10):print('\a')time.sleep(0.5)class Spider(threading.Thread):"""爬虫线程从主线程获取链接进行爬取,并处理 html 、css 文件获取新链接,以及直接下载其他文件"""def __init__(self, home_dir, home_url, link_queue, scheme, top_domain, max_tries):threading.Thread.__init__(self)self.home_dir = home_dirself.home_url = home_urlself.link_queue = link_queueself.scheme = schemeself.top_domain = top_domainself.max_tries = max_tries# 直接下载的其他文件格式self.other_suffixes = set(['js', 'jpg', 'png', 'gif', 'svg', 'json', 'xml', 'ico', 'jpeg', 'ttf', 'mp3', 'mp4', 'wav','doc', 'xls', 'pdf', 'docx', 'xlsx', 'eot', 'woff', 'csv', 'swf', 'tar', 'gz', 'zip', 'rar', 'txt','exe', 'ppt', 'pptx', 'm3u8', 'avi', 'wsf'])self.media_suffixes = set(['mp3', 'mp4', 'pdf', 'gz', 'tar', 'zip', 'rar', 'wav', 'm3u8', 'avi'])# 域名名称self.domain_names = set(['com', 'cn', 'net', 'org', 'gov', 'io'])# html 内容里的链接匹配self.html_pat = re.compile(r'(href|src)=(\"|\')([^\"\']*)')# css 内容里的链接匹配self.css_pat = re.compile(r'url\((\"|\')([^\"\']*)')self.links = set()def run(self):logger.info('{0} start.'.format(threading.current_thread().name))# 尝试从主线程的链接队列获取新链接,默认延时 60 秒结束线程while True:try:link = self.link_queue.get(timeout=60)self.spide(link)except Empty:breaklogger.info('{0} end.'.format(threading.current_thread().name))def spide(self, link):# 爬取链接,对不同链接不同处理try:suffix = link.split('.')[-1].lower()if suffix == 'css':self.handle_css(link)elif suffix in self.other_suffixes:self.download(link)else:self.handle_html(link)except:logger.error('[Unknown Error]\t{0}'.format(link))def handle_html(self, link):# 处理 html 链接html = self.get_res(link)if html is None:returnhtml_raw_links = set([ele[2] for ele in self.html_pat.findall(html)])html_raw_links = html_raw_links.union([ele[1] for ele in self.css_pat.findall(html)])if html_raw_links:# 提取有效的链接valid_links = list(filter(self.is_valid_link, html_raw_links))# 对有效的链接进行处理handled_links = list(map(self.handle_valid_link, valid_links))# 把有效的链接放入线程的 links ,供主线程爬虫管理器获取self.links = self.links.union([urljoin(link, t_link) for t_link in handled_links])# 替换 html 内容里的链接为本地网站文件夹里的相对路径html = self.replace_links(html, valid_links, self.normalize_link(link))# 保存 html 文件with open(self.make_filepath(self.normalize_link(link)), 'w') as f_w:f_w.write(html)logger.info('Handled\t{0}'.format(link))def handle_css(self, link):"""处理 css 链接"""text = self.get_res(link)if text is None:returncss_raw_links = set([ele[1] for ele in self.css_pat.findall(text)])if css_raw_links:css_raw_links = list(filter(self.is_valid_link, css_raw_links))self.links = self.links.union([urljoin(link, t_link) for t_link in css_raw_links])text = self.replace_links(text, css_raw_links, self.normalize_link(link))with open(self.make_filepath(self.normalize_link(link)), 'w') as f_w:f_w.write(text)logger.info('Handled\t{0}'.format(link))def is_valid_link(self, link):"""检测有效链接嵌入的 data:image 图片不作为新链接os.path.relpath 返回值最前面多一个 . 需要删掉"""if link.find('javascript:') >= 0 or link.find('@') >= 0 or link.find('data:image') >= 0:return Falseif link.find('http') >= 0:netloc = urlparse(link).netlocif netloc:if netloc.find(':80') > 0:netloc = netloc.replace(':80', '')return netloc[netloc.find('.') + 1:] == self.top_domainreturn Truedef handle_valid_link(self, link):"""处理链接的错误 协议 写法http:www.baidu.com http:/www.baidu.com 转换为 http://www.baidu.com"""if not link:return linkif link[0:2] == '//':return self.scheme + linkif link[0] == '/':return urljoin(self.home_url, link)if link.find('http') < 0 or link.find('http://') >= 0 or link.find('https://') >= 0:return linkif link.find('http:/') >= 0 or link.find('https:/') >= 0:return link.replace(':/', '://')if link.find('http:') >= 0 or link.find('https:') >= 0:first_colon = link.find(':')link = link[0:first_colon] + '://' + link[first_colon + 1:]return linkreturn linkdef get_res(self, link):"""获取 html 、 css 链接的响应"""num_tries = 0# 多次尝试获取while num_tries < self.max_tries:try:res = opener.open(Request(link)).read()breakexcept error.HTTPError:logger.error('[error.HTTPError]\t{0}'.format(link))return Noneexcept error.URLError:logger.error('[error.URLError]\t{0}'.format(link))return Noneexcept UnicodeEncodeError:logger.error('[UnicodeEncodeError]\t{0}'.format(link))return Noneexcept http.client.BadStatusLine:logger.error('[http.client.BadStatusLine]\t{0}'.format(link))return Noneexcept http.client.IncompleteRead:logger.error('[http.client.IncompleteRead]\t{0}'.format(link))return Noneexcept TimeoutError:logger.error('[TimeoutError]\t{0}'.format(link))num_tries += 1except socket.timeout:logger.error('[socket.timeout]\t{0}'.format(link))num_tries += 1except http.client.RemoteDisconnected:logger.error('[RemoteDisconnected]\t{0}'.format(link))num_tries += 1except ConnectionResetError:logger.error('[ConnectionResetError]\t{0}'.format(link))num_tries += 1if num_tries >= self.max_tries:logger.warning('[failed get]\t{0}'.format(link))return None# 解码响应内容try:text = res.decode('utf-8')return textexcept UnicodeDecodeError:passtry:text = res.decode('gb2312')return textexcept UnicodeDecodeError:passtry:text = res.decode('gbk')return textexcept UnicodeDecodeError:passlogger.error('[UnicodeDecodeError]\t{0}'.format(link))return Nonedef download(self, link):"""直接下载其他格式的文件"""socket.setdefaulttimeout(20)if link.split('.')[-1].lower() in self.media_suffixes:socket.setdefaulttimeout(600)num_tries = 0# 多次尝试下载while num_tries < self.max_tries:try:urlretrieve(link, self.make_filepath(link))breakexcept error.HTTPError:logger.error('[error.HTTPError]\t{0}'.format(link))breakexcept error.URLError:logger.error('[error.URLError]\t{0}'.format(link))breakexcept UnicodeEncodeError:logger.error('[UnicodeEncodeError]\t{0}'.format(link))breakexcept http.client.BadStatusLine:logger.error('[http.client.BadStatusLine]\t{0}'.format(link))breakexcept http.client.IncompleteRead:logger.error('[http.client.IncompleteRead]\t{0}'.format(link))breakexcept TimeoutError:logger.error('[TimeoutError]\t{0}'.format(link))num_tries += 1except socket.timeout:logger.error('[socket.timeout]\t{0}'.format(link))num_tries += 1except http.client.RemoteDisconnected:logger.error('[RemoteDisconnected]\t{0}'.format(link))num_tries += 1except ConnectionResetError:logger.error('[ConnectionResetError]\t{0}'.format(link))num_tries += 1if num_tries >= self.max_tries:logger.warning('[failed download]\t{0}'.format(link))logger.info('Downloaded\t{0}'.format(link))def make_filepath(self, link):"""把链接创建为本地网站文件夹的绝对路径"""# 需要的话创建新文件夹abs_filepath = self.get_abs_filepath(link)dirname = os.path.dirname(abs_filepath)if not os.path.exists(dirname):try:os.makedirs(dirname)except FileExistsError:passexcept NotADirectoryError:logger.error('[NotADirectoryError]\t{0}\t{1}'.format(link, abs_filepath))return abs_filepathdef get_abs_filepath(self, link):"""把链接转换为本地网站文件夹的绝对路径"""old_link = linkif link[-1] == '/':link += 'index.html'elif link.split('.')[-1] in self.domain_names:link += '/index.html'rel_url = os.path.relpath(link, self.home_url)if rel_url.find('?') >= 0:rel_url += '.html'if rel_url.split('/')[-1].find('.') < 0 or rel_url == '.':rel_url += 'index.html'abs_filepath = os.path.join(self.home_dir, rel_url)if abs_filepath.find('..') > 0:parts = abs_filepath.split('..')abs_filepath = '/'.join(parts[0].split('/')[0:-2]) + parts[1]if os.path.isdir(abs_filepath):logger.warning('[isdir]\t{0}\t{1}'.format(old_link, abs_filepath))abs_filepath = os.path.join(abs_filepath, 'index.html')return abs_filepathdef replace_links(self, content, links, cur_url):"""替换 html 、 css 内容里的链接"""links.sort(key=lambda link: len(link), reverse=True)for link in set(links):link_abspath = self.get_abs_filepath(urljoin(cur_url, self.normalize_link(link)))cur_url_abspath = self.get_abs_filepath(cur_url)rel_link = os.path.relpath(link_abspath, cur_url_abspath)[1:].replace('?', '%3F')replacement = '"{0}"'.format(rel_link)content = content.replace('"{0}"'.format(link),replacement).replace('\'{0}\''.format(link), replacement)return contentdef normalize_link(self, link):if link.find('http') < 0:return linkif link.find(':80') > 0:link = link.replace(':80', '')first_colon = link.find(':')link = self.scheme + link[first_colon:]return linkdef get_links(self):"""主线程爬虫管理器从这里获取爬虫子线程的新链接获取后子线程就删除旧链接,为后面获取的链接做准备"""export_links = self.links.copy()self.links.clear()return export_linksif __name__ == '__main__':manager = Manager('http://www.whsw.net/')manager.start()

Python 爬虫--网站下载器相关推荐

  1. Python爬虫----网页下载器和urllib2模块及对应的实例

    网页下载器:将互联网上URL对应的网页下载到本地的工具,是爬虫的核心组件 urllib2下载网页的三种方法 对应实例代码如下: #coding:utf8import urllib2 url = 'ht ...

  2. Python爬虫-音乐下载器

    仅供参考学习,禁止用于非法用途 目录 1.第三方库准备 安装库 requests selenium webdriver 以及 PhantomJS 2.创建目录 创建主目录Music_Downloade ...

  3. 爬虫 图片下载器:从图片分享网站下载图片并进行分类整理

    目录 前言 1. 图片下载器概述 2. 环境准备 3. 分析目标网站 4. 使用Python编写图片下载器 4.1 安装依赖库 4.2 获取图片链接 4.3 下载图片 4.4 图片分类整理 5. 总结 ...

  4. 【kimol君的无聊小发明】—用python写图片下载器

    [kimol君的无聊小发明]-用python写图片下载器 前言 一.单线程版 二.多线程版 写在最后 Tip:本文仅供学习与参考,且勿用作不法用途~ 前言 某个夜深人静的夜晚,我打开了自己的文件夹,发 ...

  5. 【kimol君的无聊小发明】—用python写视频下载器

    [kimol君的无聊小发明]-用python写视频下载器 前言 一.爬虫分析 1.视频搜索 2.视频下载 二.我的代码 写在最后 Tip:本文仅供学习与参考,且勿用作不法用途~ 前言 某个夜深人静的夜 ...

  6. 网站下载器WebZip、Httrack及AWWWB.COM网站克隆器

     动机 闲扯节点,可略读. 下载并试用这些软件并非是为了一己之私,模仿他人网站以图利.鉴于国内网络环境之艰苦,我等屌丝级半罐水程序员,纵有百度如诸葛大神万般协力相助,也似后主般无能不能解决工作和娱乐中 ...

  7. python爬虫下载-python爬虫之下载文件的方式总结以及程序实例

    python爬虫之下载文件的方式以及下载实例 目录 第一种方法:urlretrieve方法下载 第二种方法:request download 第三种方法:视频文件.大型文件下载 实战演示 第一种方法: ...

  8. python 下载文件-python爬虫之下载文件的方式总结以及程序实例

    python爬虫之下载文件的方式以及下载实例 目录 第一种方法:urlretrieve方法下载 第二种方法:request download 第三种方法:视频文件.大型文件下载 实战演示 第一种方法: ...

  9. python爬虫批量下载“简谱”

    python讨论qq群:996113038 导语: 上次发过一篇关于"python打造电子琴"的文章,从阅读量来看,我们公众号的粉丝里面还是有很多对音乐感兴趣的朋友的.于是,今天我 ...

  10. Python爬虫实战——下载小说

    Python爬虫实战--下载小说 前言 第三方库的安装 示例代码 效果演示 结尾 前言 使用requests库下载开源网站的小说 注意:本文仅用于学习交流,禁止用于盈利或侵权行为. 操作系统:wind ...

最新文章

  1. php中url重写,使用PHP重写URL
  2. VBA经典常用语句400句
  3. 全志A33-BootLoader的两个阶段:boot0和second boot
  4. 伪代码block转换成程序流程图_程序设计基础
  5. happy 2016, happy 11111100000
  6. 多线程基础(三)NSThread基础
  7. java怎么缓存数据_java中的缓存技术该如何实现
  8. AspNetPager分页控件的使用以及常见错误
  9. 经典回味:ResNet到底在解决一个什么问题呢?
  10. 最简单易懂的git介绍
  11. 微信内网页分享,分享者能看到分享的图片(描述),但被分享者无法看到
  12. 塔夫斯大学计算机教授,观汗辨健康?美国塔夫茨大学发明了一种汗液传感器
  13. mysql 把表名改成大写_mysql 把表名自动改为大写
  14. 量化投资学习-7:图解股市的基本面、技术面、市场环境面的关系
  15. Eclipse的TPTP使用方法
  16. [原创]隐身斗篷简介及仿真
  17. Python AST node转为string(source code)
  18. python做菜单_python实现三级菜单
  19. iframe解决跳转登录界面问题
  20. linux如何对mysql进行定时备份并定时清理3天之前的备份数据

热门文章

  1. mujoco安装教程
  2. 常见计算机录制屏幕软件名称,录制电脑屏幕视频的软件有什么?
  3. 分享:第十届“泰迪杯”数据挖掘挑战赛优秀作品--A1-基于深度学习的农田害虫定位与识别研究(一)
  4. 第十届泰迪杯数据挖掘挑战赛A题解题思路附代码
  5. 笔记 神经网络、BP算法推导
  6. LU分解算法(串行、并行)
  7. ouravr某PCB高手发言总结
  8. (安徽省地图)行政区划图高清矢量cdr|pdf(详细版2021年)
  9. YDOOK:MyPLayer:Jinwei Lin 最新开源 Python 音频视频基本播放器
  10. 计算机毕业设计、课程设计、实战项目之[含论文+答辩PPT+任务书+中期检查表+源码等]基于ssm的NBA球队管理系统