目录
1. Scrapy简介与实例解析
对于一个小型的爬虫项目,一般使用python的requests库+一个解析html文件库(例如lxml,BeautifulSoup等)即可,对于大型的项目我们可以使用专业的爬虫框架,例如Scrapy。
什么是爬虫框架:
- 爬虫框架是实现爬虫功能的一个软件结构和功能组件集合。
- 爬虫框架是一个半成品,能够帮助用户实现专业网络爬虫。
Scrapy是一个功能强大的爬虫框架,下图展示了Scrapy爬虫框架的结构(该图来源与中国慕课大学)
在这个框架中,大部分功能都已经为我们实现好了,我们只需自定义SPIDERS以及ITEM PIPELINES两个模块即可。
如果需要下载Scrapy直接 pip install scrapy
即可
接下来说下本实例的一个大致需求与解决思路:
- 需求:需要从一个网址上爬取所有的下载文件,该网址还有很多的子目录,子目录下的文件也要下载,下载后要按照原网址的目录进行摆放。下载完后需要定时去检查更新文件。
* 思路:首先爬取网址的html文件,解析a标签(实例中的爬取网址信息都存在a标签中)获取所有需要下载的文件链接,包括子文件夹下的文件链接。通过scrapy提供的FilesPipeline下载文件。爬取后使用apscheduler包定时爬取更新(后面定时爬取的部分是使用python调用本地wget工具下载的,因为当时的下载源有几个文件始终下载不下来,但使用wget可以下载)。
2. 解析html文件中的下载地址
下面我写了一个递归函数来遍历提供url网站中的所有可下载的文件地址(包括子文件夹中的文件)。通过python的request库发起请求获取html文件,然后通过lxml的etree包解析html文件,获取其中所有的a标签的href属性信息(因为下载链接都放在href属性中)。
1)如果a标签href属性为 ../
说明是返回上一层的标签,无用,舍去
2)排除 ../
后, 如果a标签href属性的最后一个元素为 /
说明是下一层目录的标签,此时调用自身开始递归。
3)如果a标签href属性都不在以上情况中,那么说明就是当前文件目录中需要下载的文件,我们存入到file_links列表中。
def get_all_files_link(url):
html_file = requests.get(url)
if html_file.status_code != 200:
print("请求{}失败".format(url))
html_parse = etree.HTML(html_file.content.decode('utf-8'))
items = html_parse.xpath("//a/@href") # 寻找所有的a标签的文本内容
file_links = []
for item in items:
if item == "../":
continue
if item[-1] == "/":
links = get_all_files_link(url+item)
if len(links) > 0:
file_links.extend(links)
continue
url_path = url + item
file_links.append(url_path)
return file_links
3. 对比文件是否需要更新
在上一步获取了所需需要下载的文件链接,接下来对比下远程端与本地端的文件大小是否相同(也可以使用文件的更新时间来判断)。对于远程端,我们直接获取header中的length参数即可,对于本地的文件,先去检查是否存在,如果存在,获取文件的资源大小信息与远程端的进行对比,如果不同说明需要更新。这里建议使用多线程的方式去做,相比单线程能够提速10倍左右。因为刚接触scrapy,对scarpy的中间件不是很了解,所以没用scrapy而是自己写的脚本,如果有知道使用scrapy更新文件的大神请赐教(我之前发现scrapy有一个更新策略是“过期更新”,即下载的文件保存超过一定时间后,才允许去更新,这明显与我的需求不符,而且scrapy在底层判断文件是否过期很慢,没看底层暂不知道为什么)。
def check_whether_update(self, url_path):
"""
检查网络文件与本地文件是否一致(通过文件大小判断),
若与本地文件不同或者本地没有该文件,返回True(需要重新下载)
若相同返回False(不需要重新下载)
"""
request_url = request.urlopen(url_path)
url_file_size = request_url.length # 获取网络上的资源文件大小
local_file_path = path.join(FILES_STORE,
url_path.split(self.url_head)[-1])
if path.exists(local_file_path) is False:
return True
local_file_size = stat(local_file_path).st_size # 获取本地文件资源大小
if url_file_size == local_file_size:
print("{}该文件已是最新状态,无需下载".format(url_path))
return False
else:
return True
4. 使用Scarpy爬取文件
经过以上两步,我们知道了哪些文件需要去下载(或更新),那么接下来使用scrapy创建一个爬虫来下载。
创建爬虫
安装好scrapy后进入到你的项目文件夹,打开命令行,输入指令创建一个爬虫项目
scrapy startproject scrapytest
创建项目后,cd进入第一层文件夹,然后输入以下指令进行创建一个爬虫
scrapy genspider testone web_url
其中,testone是创建的爬虫名称,web_url是要爬的网址
创建的整个项目结构如下所示:
└─scrapytest
├──scrapy.cfg 部署scrapy爬虫的配置文件
└──scrapytest scrapy框架的用户自定义python代码
├──_init_.py 初始化脚本
├──pipelines.py pipelines代码模板(继承类)
├──middleware.py middlewares代码模板(继承类)
├──items.py items代码模板(继承类)
├──setting.py scrapy爬虫的配置文件
└──spiders spiders代码模板目录(继承类)
├──_init_.py 初始化文件
└──testone.py 用户自定义爬虫文件(继承类)
settings.py
创建爬虫后,我们首先来配置下settings文件,这里主要是配置了下存储的目录位置,以及下载文件大小的限制,其他的参数均取默认值。
# -*- coding: utf-8 -*-
# Scrapy settings for scrapytest project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
# https://docs.scrapy.org/en/latest/topics/settings.html
# https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
# https://docs.scrapy.org/en/latest/topics/spider-middleware.html
import random
BOT_NAME = 'scrapytest'
SPIDER_MODULES = ['scrapytest.spiders']
NEWSPIDER_MODULE = 'scrapytest.spiders'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'scrapytest (+http://www.yourdomain.com)'
# Obey robots.txt rules
ROBOTSTXT_OBEY = True
# Configure maximum concurrent requests performed by Scrapy (default: 16)
CONCURRENT_REQUESTS = 5
# Configure a delay for requests for the same website (default: 0)
# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
#DOWNLOAD_DELAY = 3
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16
# Disable cookies (enabled by default)
#COOKIES_ENABLED = False
# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = False
# Override the default request headers:
#DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
#}
# Enable or disable spider middlewares
# See https://docs.scrapy.org/en/latest/topics/spider-middleware.html
# SPIDER_MIDDLEWARES = {
# 'scrapytest.middlewares.ScrapytestSpiderMiddleware': 543,
# }
# Enable or disable downloader middlewares
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
# 'scrapytest.middlewares.ScrapytestDownloaderMiddleware': 543,
#}
# Enable or disable extensions
# See https://docs.scrapy.org/en/latest/topics/extensions.html
#EXTENSIONS = {
# 'scrapy.extensions.telnet.TelnetConsole': None,
#}
# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'scrapytest.pipelines.ScrapytestPipeline': 1,
}
FILES_STORE = 'E:/scrapy_download/'
FILES_EXPIRES = 0 # 设置文件过期时间
DOWNLOAD_WARNSIZE = 1572864000 # 文件过大警告,1.5GB
DOWNLOAD_MAXSIZE = 2097152000 # 下载最大文件不能超过2G
# RETRY_ENABLED = False
DOWNLOAD_TIMEOUT = 36000 # 设置下载超时配置,30分钟
USER_AGENT_LIST = [
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36'
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
]
USER_AGENT = random.choice(USER_AGENT_LIST) # 随机选取一个代理
# Enable and configure the AutoThrottle extension (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/autothrottle.html
#AUTOTHROTTLE_ENABLED = True
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
#AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
#AUTOTHROTTLE_DEBUG = False
# Enable and configure HTTP caching (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = 'httpcache'
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
items.py
接着,我们来创建一个items用来存储在爬虫文件中获取的所有文件下载地址信息。其中files用来保存爬取的文件信息,urls保存文件的下载地址,paths保存文件保存的路径。
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
class ScrapytestItem(scrapy.Item):
# define the fields for your item here like:
files = scrapy.Field()
file_urls = scrapy.Field()
file_paths = scrapy.Field()
testone.py
接着编写我们创建的爬虫文件,这里就是按照之前的思路:
1)遍历所有需要下载的文件url
2)比对每一个远程端与本地文件,看是否需要更新
3)将需要下载的文件url存入自定义item类
4)将自定义item类信息传给pipelines处理
# -*- coding: utf-8 -*-
import scrapy
from ..items import ScrapytestItem
import requests
from ..settings import FILES_STORE
from os import stat, path
import threading
from lxml import etree
class TestoneSpider(scrapy.Spider):
name = 'testone'
# allowed_domains = ['']
start_urls = ['https://you_want_to_download']
def parse(self, response):
# 遍历获取所有需要下载的文件
files_link = get_all_files_link(response.url)
# 使用多线程去检查哪些文件是更新了的,如果没有更新就无需再次下载
download_files = multi_check_file(files_link)
print("需要下载/更新的文件包括:")
for v in download_files:
print(v)
items = ScrapytestItem()
items["file_urls"] = download_files
yield items
def get_all_files_link(url):
html_file = requests.get(url)
if html_file.status_code != 200:
print("请求{}失败".format(url))
html_parse = etree.HTML(html_file.content.decode('utf-8'))
items = html_parse.xpath("//a/@href") # 寻找所有的a标签的文本内容
file_links = []
for item in items:
if item == "../":
continue
if item[-1] == "/":
links = get_all_files_link(url+item)
if len(links) > 0:
file_links.extend(links)
continue
url_path = url + item
file_links.append(url_path)
return file_links
class MyThread(threading.Thread):
def __init__(self, func, args=()):
super(MyThread, self).__init__()
self.func = func
self.args = args
self.result = None
def run(self):
self.result = self.func(*self.args)
def get_result(self):
try:
return self.result
except Exception:
return None
def multi_check_file(files_link):
download_list = []
max_task = 50
tasks = []
flag_list = []
for index, file_link in enumerate(files_link):
tasks.append(MyThread(check_whether_update, args=(file_link,)))
# 每创建50个多线程或者已经遍历到最后一个元素,启动多线程
if (index+1) % max_task == 0 or index+1 == len(files_link):
[task.start() for task in tasks] # 启动多线程
[task.join() for task in tasks] # 等待多线程结束
flag_list.extend([task.get_result() for task in tasks]) # 获取多线程结果
tasks = [] # 清空任务,准备下一批多线程任务
if len(flag_list) != len(files_link):
print("multi_check_file failed!")
exit(2)
for index in range(len(files_link)):
if flag_list[index] is True:
download_list.append(files_link[index])
return download_list
def check_whether_update(url_path):
"""
检查网络文件与本地文件是否一致(通过文件大小判断),
若与本地文件不同或者本地没有该文件,返回True(需要重新下载)
若相同返回False(不需要重新下载)
"""
url_head = "https://you_want_to_download"
response = requests.head(url_path)
url_file_size = int(response.headers["content-length"]) # 获取网络上的资源文件大小
local_file_path = path.join(FILES_STORE,
url_path.split(url_head)[-1])
if path.exists(local_file_path) is False:
return True
local_file_size = stat(local_file_path).st_size # 获取本地文件资源大小
if url_file_size == local_file_size:
# print("{}该文件已是最新状态,无需下载".format(url_path))
return False
else:
return True
pipelines.py
在上一步中通过yield生成器将自定义item信息传入pipelines,接下来我们来通过scrapy提供的FilesPipeline下载文件,这里我们定义的类需要继承来自于FilesPipeline父类。然后需要实现三个方法file_path, get_media_request, item_completed. 其中file_path使用来指定保存文件的路径(这个路径是相对路径,是在settings中FILES_STORE字段下的目录),get_media_request方法是通过传入的自定义item的urls字段去下载文件,item_completed方法是将file_path解析的相应文件存储路径保存在自定义item中。
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
import os
import scrapy
from scrapy.pipelines.files import FilesPipeline
from scrapy.exceptions import DropItem
class ScrapytestPipeline(FilesPipeline):
url_head = 'https://you_want_to_download'
def file_path(self, request, response=None, info=None):
return request.url.split(self.url_head)[-1]
def get_media_requests(self, item, info):
for file_url in item['file_urls']:
yield scrapy.Request(file_url)
def item_completed(self, results, item, info):
file_paths = [x['path'] for ok, x in results if ok]
if not file_paths:
raise DropItem("Item contains no files")
item['file_paths'] = file_paths
return item
启动爬虫
通过以上步骤,我们的爬虫项目就简单的完成了,接下来,启动爬虫爬取文件。这里提供两种方法:
1)通过命令行窗口输入scrapy指令启动(在scrapytest的第一级目录下)
scrapy crawl testone
2)通过python调用本地指令启动(这个好处在于可以使用ide调试)创建一个run.py脚本(在scrapytest的第一级目录下),内容如下:
from scrapy import cmdline
cmdline.execute("scrapy crawl testone".split())
5. 使用Pyhton+wget下载文件
对于一般爬虫项目,使用以上scrapy脚本基本就能满足需求了,但是之前爬取的资源中,有些文件始终下载不下来(下载下来的文件与远程端的文件大小不一致,无法打开),我有尝试过使用python的urllib、requests包、本地的curl工具等都无法下载。但是使用chrome浏览器以及wget工具是可以下载的。所以这里我就针对某些下载不了的资源使用pyhton+wget的方式下载。
使用wget下载新文件
首先确保本机下载了wget工具,下面是使用python3.6的subprocess库调用本地wget工具进行下载的,注意使用subprocess时command的编码方式,Linux和Windows是不同的,Linux直接使用utf-8编码即可,但windows必须使用gbk编码(坑)。
file_dir = path.join(FILES_STORE, "/".join(url.split(start_url)[-1].split("/")[:-1]))
command = "wget -P {} {} --no-check-certificate".format(file_dir, url).encode('gbk')
try:
# 注意sub.run方法在linux中和windows中的编码方式不同,linux是utf-8,windows是gbk
res = sub.run(command.decode('gbk'), shell=True, stdout=sub.PIPE,
stderr=sub.PIPE, timeout=3600, encoding='gbk')
# 检查文件是否与下载源提供的大小相同
response = requests.head(url)
url_file_size = int(response.headers["content-length"]) # 获取网络上的资源文件大小
local_file_size = stat(path.join(file_dir, url.split("/")[-1])).st_size # 获取本地文件资源大小
if url_file_size == local_file_size:
logging.debug("successful download: ", url.split(start_url)[-1])
else:
logging.error("error: 下载的文件大小与下载源提供的content-length不等")
os.remove(file_dir + url.split("/")[-1]) # 删除该文件
continue
except Exception as e:
logging.error(e)
使用wget更新文件
其实这里与上面的下载方式几乎相同,不同的点在于:
1)使用的wget指令有些不同,这里是 -O
2)由于是更新文件,为了不影响原先下载文件,这里是先下载在后缀为.cache
的文件中,等下载完成后验证没问题再将原来的文件替换掉。
file_path = path.join(FILES_STORE, url.split(start_url)[-1])
# 下载到.cache文件,等下载完后在替换
command = "wget -O {} {} --no-check-certificate".format(file_path + ".cache", url).encode('gbk')
try:
# 注意sub.run方法在linux中和windows中的编码方式不同,linux是utf-8,windows是gbk
res = sub.run(command.decode('gbk'), shell=True, stdout=sub.PIPE,
stderr=sub.PIPE, timeout=3600, encoding='gbk')
# 检查文件是否与下载源提供的大小相同
response = requests.head(url)
url_file_size = int(response.headers["content-length"]) # 获取网络上的资源文件大小
local_file_size = stat(file_path + ".cache").st_size # 获取本地文件资源大小
if url_file_size == local_file_size:
logging.info("successful update: ", url.split(start_url)[-1])
else:
logging.error("error: 下载的文件大小与下载源提供的content-length不等")
os.remove(file_path + ".cache")
continue
# 下载完成,先删除旧文件,在重命名
os.remove(file_path)
os.rename(file_path + ".cache", file_path)
except Exception as e:
logging.error(e)
6. 使用apscheduler定时更新文件
关于定时更新文件,直接使用apscheduler库即可,可以定时去调用之前讲的scrapy爬虫服务或者使用python+wget的方式,都可以。下面就是已python+wget的方式为例,代码和之前的一样,只不过添加了apscheduler服务而以。
import requests
from os import stat, path
import os
from scrapytest.settings import FILES_STORE
import threading
from lxml import etree
import subprocess as sub
from apscheduler.schedulers.blocking import BlockingScheduler
from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR
import logging
# 配置log信息
logging.basicConfig(level=logging.INFO,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
filename='update_androidSDK_log.txt',
filemode='a')
def get_all_files_link(url):
"""
遍历url下所有需要下载的文件地址
"""
html_file = requests.get(url)
if html_file.status_code != 200:
logging.warning("请求{}失败".format(url))
html_parse = etree.HTML(html_file.content.decode('utf-8'))
items = html_parse.xpath("//a/@href") # 寻找所有的a标签的文本内容
file_links = []
for item in items:
if item == "../":
continue
if item[-1] == "/":
links = get_all_files_link(url + item)
if len(links) > 0:
file_links.extend(links)
continue
url_path = url + item
file_links.append(url_path)
return file_links
class MyThread(threading.Thread):
"""
自定义多线程类,方便获取每个线程的返回结果
"""
def __init__(self, func, args=()):
super(MyThread, self).__init__()
self.func = func
self.args = args
self.result = None
def run(self):
self.result = self.func(*self.args)
def get_result(self):
try:
return self.result
except Exception:
return None
def multi_check_file(files_link):
"""
使用多线程的方式,遍历所有下载源的header信息,
检查是否有需要更新的文件,或者需要下载的新文件
"""
download_list = []
update_list = []
max_task = 50
tasks = []
flag_list = []
for index, file_link in enumerate(files_link):
tasks.append(MyThread(check_whether_update, args=(file_link,)))
# 每创建50个多线程或者已经遍历到最后一个元素,启动多线程
if (index + 1) % max_task == 0 or index + 1 == len(files_link):
[task.start() for task in tasks] # 启动多线程
[task.join() for task in tasks] # 等待多线程结束
flag_list.extend([task.get_result() for task in tasks]) # 获取多线程结果
tasks = [] # 清空任务,准备下一批多线程任务
if len(flag_list) != len(files_link):
logging.error("multi_check_file failed!")
exit(2)
for index in range(len(files_link)):
if flag_list[index] is 1: # 文件不存在需要下载
download_list.append(files_link[index])
elif flag_list[index] is 2: # 文件已过期,需要重新下载
update_list.append(files_link[index])
return download_list, update_list
def check_whether_update(url_path):
"""
检查网络文件与本地文件是否一致(通过文件大小判断),
若与本地文件不同或者本地没有该文件,返回True(需要重新下载)
若相同返回False(不需要重新下载)
return:
code:
0 文件已是最新,无需重新下载
1 文件不存在需要下载
2 文件已过期,需要重新下载
"""
url_head = "https://you_want_to_download"
response = requests.head(url_path)
url_file_size = int(response.headers["content-length"]) # 获取网络上的资源文件大小
local_file_path = path.join(FILES_STORE,
url_path.split(url_head)[-1])
if path.exists(local_file_path) is False:
return 1 # 文件不存在需要下载
local_file_size = stat(local_file_path).st_size # 获取本地文件资源大小
if url_file_size == local_file_size:
logging.info("{}该文件已是最新状态,无需下载".format(url_path))
return 0 # 文件已是最新,无需重新下载
else:
return 2 # 文件已过期,需要重新下载
def task():
"""
定期执行的任务
"""
start_url = 'https://you_want_to_download'
files_link = get_all_files_link(start_url) # 获取所有需要下载的文件url
# 使用多线程去检查哪些文件是更新了的,哪些是新文件,如果没有更新就无需再次下载
download_files, update_files = multi_check_file(files_link)
logging.info("需要下载的文件有{}项:".format(len(download_files)))
for v in download_files:
logging.info(v)
logging.info("需要更新的文件有{}项:".format(len(update_files)))
for v in update_files:
logging.info(v)
# 下载新文件
for url in download_files:
file_dir = path.join(FILES_STORE, "/".join(url.split(start_url)[-1].split("/")[:-1]))
command = "wget -P {} {} --no-check-certificate".format(file_dir, url).encode('gbk')
try:
# 注意sub.run方法在linux中和windows中的编码方式不同,linux是utf-8,windows是gbk
res = sub.run(command.decode('gbk'), shell=True, stdout=sub.PIPE,
stderr=sub.PIPE, timeout=3600, encoding='gbk')
# 检查文件是否与下载源提供的大小相同
response = requests.head(url)
url_file_size = int(response.headers["content-length"]) # 获取网络上的资源文件大小
local_file_size = stat(file_dir + url.split("/")[-1]).st_size # 获取本地文件资源大小
if url_file_size == local_file_size:
logging.debug("successful download: ", url.split(start_url)[-1])
else:
logging.error("error: 下载的文件大小与下载源提供的content-length不等")
os.remove(file_dir + url.split("/")[-1]) # 删除该文件
continue
except Exception as e:
logging.error(e)
# 更新文件
for url in update_files:
file_path = path.join(FILES_STORE, url.split(start_url)[-1])
# 下载到.cache文件,等下载完后在替换
command = "wget -O {} {} --no-check-certificate".format(file_path + ".cache", url).encode('gbk')
try:
# 注意sub.run方法在linux中和windows中的编码方式不同,linux是utf-8,windows是gbk
res = sub.run(command.decode('gbk'), shell=True, stdout=sub.PIPE,
stderr=sub.PIPE, timeout=3600, encoding='gbk')
# 检查文件是否与下载源提供的大小相同
response = requests.head(url)
url_file_size = int(response.headers["content-length"]) # 获取网络上的资源文件大小
local_file_size = stat(file_path + ".cache").st_size # 获取本地文件资源大小
if url_file_size == local_file_size:
logging.info("successful update: ", url.split(start_url)[-1])
else:
logging.error("error: 下载的文件大小与下载源提供的content-length不等")
os.remove(file_path + ".cache")
continue
# 下载完成,先删除旧文件,在重命名
os.remove(file_path)
os.rename(file_path + ".cache", file_path)
except Exception as e:
logging.error(e)
def my_listener(event):
if event.exception:
logging.error('error')
else:
logging.info('-------successful-------')
if __name__ == "__main__":
# By default, only one instance of each job is allowed to be run at the same time.
# This means that if the job is about to be run but the previous run hasn’t finished yet,
# then the latest run is considered a misfire.
# 当前一个任务没有完成,下一个任务会跳过
scheduler = BlockingScheduler(timezone="Asia/Shanghai")
# 每天的指定时间开始更新数据
scheduler.add_job(func=task, id='day_job', trigger='cron',
year="*", month="*", day="*",
hour="1", minute="0", second="0")
scheduler.add_listener(my_listener, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR)
scheduler._logger = logging
# 启动
scheduler.start()