目錄
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(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()