一、腳本功能概述
這個 Python 腳本的主要功能是從 GitHub 上下載指定項目的各個發布版本的壓縮包(.zip
?和?.tar.gz
?格式)。用戶需要提供兩個參數:一個是包含項目信息的 CSV 文件,另一個是用于保存下載版本信息的 CSV 文件。腳本會遍歷項目列表,訪問每個項目的?tags
?頁面,下載所有可用的版本壓縮包,并記錄相關信息到指定的 CSV 文件中。
二、腳本使用說明
在運行腳本前,請確保你已經安裝了?requests
?和?beautifulsoup4
?庫。如果未安裝,可以使用以下命令進行安裝:
bash
pip install requests beautifulsoup4
運行腳本時,在命令行中輸入以下格式的命令:
bash
python script.py project_list.csv save_info.csv
其中,script.py
?是腳本文件名,project_list.csv
?是包含項目信息的 CSV 文件,save_info.csv
?是用于保存下載版本信息的 CSV 文件。
下面是完整的腳本。
import requests
from bs4 import BeautifulSoup
import os
import csv
import sys
import time
import random
from urllib.error import HTTPError
import signal
# 設置GitHub API的個人訪問令牌
# 從這里獲取:https://github.com/settings/tokens
access_token = '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'
# 分頁參數
page = 1
num = 0
savelastfilename =""
lastfilename = ""
url_with_page = ""
fieldnames = ['項目名稱', 'tags', '版本號', '壓縮包名','是否有發布']
file = None
writer = None
proxies = {
? ? "https1": "https://182.204.177.61:4331",
? ? "https2": "https://140.255.150.253:4361",
? ? "https3": "https://113.231.18.51:4334",
? ? "https4": "https://116.7.192.240:43581",
? ? "https5": "https://121.61.160.170:43311",
? ? "https6": "https://124.231.69.245:43311",
? ? "https7": "https://183.128.97.139:43251",
? ? "https8": "https://124.94.188.113:43341",
? ? "https9": "https://1.82.107.78:4389",
? ? "https10": "https://1.82.107.49:4379",
}
headers = {
? ? 'User-Agent': 'Mozilla/5.0',
? ? 'Authorization': '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',
? ? 'Content-Type': 'text/html',
? ? 'Accept': 'application/json'
}
# 定義信號處理函數
def signal_handler(sig, frame):
? ? print("\n收到了中斷信號,程序退出!!!")
? ? sys.exit(0)
# 注冊信號處理函數
signal.signal(signal.SIGINT, signal_handler)
def get_html_url_with_tags(file_name):
? ? html_urls = []
? ? Name = ''
? ? with open(file_name, 'r', newline='') as file:
? ? ? ? reader = csv.DictReader(file)
? ? ? ? for row in reader:
? ? ? ? ? ? Name = row.get('Name')
? ? ? ? ? ? html_url = row.get('HTML URL')
? ? ? ? ? ? if html_url:
? ? ? ? ? ? ? ? new_html_url = html_url + "/tags"
? ? ? ? ? ? ? ? html_urls.append(new_html_url)
? ? ? ? ? ? ? ? #print("Name="+ Name +" ?url=" + new_html_url)
? ? return Name,html_urls
def download_file(url, save_path, timeout=20, max_retries=3):
? ? retries = 0
? ? while retries < max_retries:
? ? ? ? try:
? ? ? ? ? ? # 發送GET請求獲取文件內容,設置超時時間
? ? ? ? ? ? #@retry(tries=3, delay=2) ?# 重試3次,每次間隔2秒
? ? ? ? ? ? #response = requests.get(url, proxies=proxies, headers=headers, timeout=15)
? ? ? ? ? ? response = requests.get(url, proxies=proxies, timeout=15)
? ? ? ? ? ? # 檢查響應狀態碼
? ? ? ? ? ? if response.status_code == 200:
? ? ? ? ? ? ? ? # 寫入文件
? ? ? ? ? ? ? ? print(f"正在下載文件{save_path}...",end='')
? ? ? ? ? ? ? ? with open(save_path, 'wb') as f:
? ? ? ? ? ? ? ? ? ? f.write(response.content)
? ? ? ? ? ? ? ? print(f"...文件下載完成")
? ? ? ? ? ? ? ? return True
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? print(f"下載失敗:狀態碼 {response.status_code}")
?? ??? ??? ??? ?#print("下載失敗:超過最大重試次數")
? ? ? ? ? ? ? ? check_connection_error(response)
? ? ? ? except requests.exceptions.Timeout:
? ? ? ? ? ? print(f"請求超時,正在嘗試重試...", end="")
? ? ? ? except requests.exceptions.RequestException as e:
? ? ? ? ? ? print(f"請求異常:{e}", end="")
? ? ? ? retries += 1
? ? ? ? print(f"重試次數:{retries}")
? ??
? ? return False
def check_connection_error(response):
? ? """檢查是否由于連接問題而無法訪問 GitHub"""
? ? if response.status_code == 200:
? ? ? ? print("返回200!")
? ? ? ? return True
? ? if response.status_code == 403:
? ? ? ? print("已達到 GitHub API 請求限制!")
? ? ? ? return False
? ? elif response.status_code == 400:
? ? ? ? print("服務器無法理解請求!")
? ? ? ? return False
? ? elif response.status_code == 502:
? ? ? ? print("遠程服務器關閉了連接。")
? ? ? ? return False
? ? elif response.status_code == 404:
? ? ? ? print("未找到請求的資源。")
? ? ? ? return False
? ? elif response.status_code == 407:
? ? ? ? print("代理服務器需要身份驗證!")
? ? ? ? return False
? ? elif response.status_code == 406:
? ? ? ? print("客戶端請求問題,可能是代理失效,建議更換代理IP列表")
? ? ? ? return False
? ? elif response.status_code == 429:
? ? ? ? print("請求過多,請稍后重試。")
? ? ? ? return False
? ? elif response.status_code == 504:
? ? ? ? print("網關超時,等等并重試或更換其它代理!")
? ? ? ? return False
? ? elif response.status_code >= 500:
? ? ? ? print("遠程服務器內部錯誤。")
? ? ? ? return False
? ? else:
? ? ? ? try:
? ? ? ? ? ? print("遠程服務器返回未知錯誤,正在嘗試獲取更多詳細信息...")
? ? ? ? ? ? response.raise_for_status() ?# 如果請求失敗,這將拋出一個 HTTPError 異常
? ? ? ? ? ? print("獲取詳細信息失敗,當前狀態碼為:", response.status_code)
? ? ? ? ? ? return False ?# 如果無法獲取詳細信息,則返回 False,表示請求失敗
? ? ? ? except HTTPError as e:
? ? ? ? ? ? print("發生了 HTTPError 異常:", e)
? ? ? ? ? ? return False ?# 如果拋出 HTTPError 異常,則返回 False,表示請求失敗
? ? ? ? except ConnectionError as ce:
? ? ? ? ? ? print("連接被遠程服務器關閉,沒有返回任何響應。")
? ? ? ? ? ? return False ?# 如果捕獲到 ConnectionError 異常,則返回 False,表示請求失敗
? ? ? ? except requests.exceptions.Timeout:
? ? ? ? ? ? print("連接超時:可能是由于網絡問題導致的連接失敗")
? ? ? ? ? ? return False
? ? ? ? except requests.exceptions.ConnectionError:
? ? ? ? ? ? print("連接錯誤:無法連接到服務器")
? ? ? ? ? ? return False
? ? ? ? except requests.exceptions.RequestException as e:
? ? ? ? ? ? print("請求異常,最可能是代理問題:", e)
? ? ? ? ? ? return False
def wait_and_retry(wait_time=30):
? ? """等待一段時間后重試請求"""
? ? print(f"等待 {wait_time} 秒后重試...")
? ? time.sleep(wait_time)
def get_github_rate_limit(headers):
? ? url = "https://api.github.com/rate_limit"
? ? headers = {
? ? ? ? 'User-Agent': 'Mozilla/5.0',
? ? ? ? 'Authorization': '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',
? ? ? ? 'Content-Type': 'text/html',
? ? ? ? 'Accept': 'application/json'
? ? }
? ? #response = requests.get(url, proxies=proxies, headers=headers)
? ? response = requests.get(url, proxies=proxies)
? ? data = response.json()
? ? limit = data["rate"]["limit"]
? ? remaining = data["rate"]["remaining"]
? ? reset_time = data["rate"]["reset"]
? ? print(f"限速檢查完成...")
? ? return limit, remaining, reset_time?
def update_ifcheck_value(file_name, Name):
? ? """打開 CSV 文件,將指定 Name 對應的行的 ifcheck 字段值修改為 1"""
? ? rows = []
? ? with open(file_name, 'r', newline='') as file:
? ? ? ? reader = csv.DictReader(file)
? ? ? ? fieldnames = reader.fieldnames ?# 獲取表頭字段名
? ? ? ? for row in reader:
? ? ? ? ? ? if row.get('Name') == Name:
? ? ? ? ? ? ? ? row['ifcheck'] = '1' ?# 將 ifcheck 字段值修改為 1
? ? ? ? ? ? rows.append(row)
? ? # 寫回 CSV 文件
? ? with open(file_name, 'w', newline='') as file:
? ? ? ? writer = csv.DictWriter(file, fieldnames=fieldnames)
? ? ? ? writer.writeheader()
? ? ? ? writer.writerows(rows)
def renamefile(name,filename):
? ? current_path = os.getcwd()
? ? old_name = filename
? ? # 設置新的文件名
? ? new_name = name + "_" + filename
? ? # 構建新文件的完整路徑
? ? new_path = os.path.join(current_path, new_name)
? ? # 構建舊文件的完整路徑
? ? old_path = os.path.join(current_path, old_name)
? ? # 重命名文件
? ? if os.path.isfile(new_path):
? ? ? ? return False
? ? else:
? ? ? ? os.rename(old_path, new_path)
? ? ? ? return True
def analyze_download_links(Name, url):
? ? global lastfilename,num,headers
? ? global writer,file
? ? while True:
? ? ? ??
? ? ? ? #判斷是否為多頁
? ? ? ? if num == 20:
? ? ? ? ? ? url_with_page = url + "?after=" + lastfilename
? ? ? ? ? ? num = 0
? ? ? ? else:
? ? ? ? ? ? url_with_page = url
? ? ? ? print("訪問的url=" + url_with_page)
? ? ? ??
?? ??? ?#判斷是否被限速,被限速的話,等待10~20秒
? ? ? ? limit, remaining, reset_time=get_github_rate_limit(headers)
? ? ? ? if remaining == 0:
? ? ? ? ? ? count = 1
? ? ? ? ? ? time.sleep(random.randint(10, 20))
? ? ? ? ? ? print("剩余請求次數為 0 了,現在更新header" )
? ? ? ? ? ? headers = {
? ? ? ? ? ? ? ? 'User-Agent': 'User-Agent:Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0',
? ? ? ? ? ? ? ? 'Authorization': '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',
? ? ? ? ? ? ? ? 'Content-Type': 'text/html',
? ? ? ? ? ? ? ? 'Accept': 'application/json'
? ? ? ? ? ? }
?? ??? ?
?? ??? ?#通過一個代理列表中中的一個代理獲取當前url項目的tags頁面
? ? ? ? #response = requests.get(url_with_page,proxies=proxies, headers=headers)
?? ??? ?#直接獲取當前url項目的tags頁面
? ? ? ? response = requests.get(url_with_page,proxies=proxies)
? ? ? ? #進行容錯判斷,如果鏈接錯誤,則等一會重新鏈接
? ? ? ? print("進行服務器返回檢查中..." )
? ? ? ? if False == check_connection_error(response):
? ? ? ? ? ? wait_and_retry()
? ? ? ? ? ? print("服務器返回錯誤,請稍等一會..." )
? ? ? ? ? ? #time.sleep(random.randint(10, 20))
? ? ? ? ? ? headers = {
? ? ? ? ? ? ? ? 'User-Agent': 'Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11',
? ? ? ? ? ? ? ? 'Authorization': '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',
? ? ? ? ? ? ? ? 'Content-Type': 'text/html',
? ? ? ? ? ? ? ? 'Accept': 'application/json'
? ? ? ? ? ? }
? ? ? ? ? ? continue
?? ??? ?#如果沒有響應,則10~20秒,更換header重新鏈接
? ? ? ? if response is None:
? ? ? ? ? ? # Exit loop if HTTP Error 422
? ? ? ? ? ? print("GitHub 未響應")
? ? ? ? ? ? time.sleep(random.randint(10, 20))
? ? ? ? ? ? headers = {
? ? ? ? ? ? ? ? 'User-Agent': 'Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11',
? ? ? ? ? ? ? ? 'Authorization': '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',
? ? ? ? ? ? ? ? 'Content-Type': 'text/html',
? ? ? ? ? ? ? ? 'Accept': 'application/json'
? ? ? ? ? ? }
? ? ? ? ? ? continue
? ? ? ? #如果返回200,說明請求正確,處理返回數據
? ? ? ? if response.status_code == 200:
? ? ? ? ? ? soup = BeautifulSoup(response.text, 'html.parser')
? ? ? ? ? ? #如果項目tags中沒有版本,則返回 ? ??
? ? ? ? ? ? if "There aren’t any releases here" in response.text:
? ? ? ? ? ? ? ? with open(save_file_name, 'a', newline='') as file:
? ? ? ? ? ? ? ? ? ? writer = csv.writer(file)
? ? ? ? ? ? ? ? ? ? writer.writerow([Name, url, '', '','0'])
? ? ? ? ? ? ? ? print("當前項目tags下無發布版本")
? ? ? ? ? ? ? ? break
? ? ? ? ? ? #H抓取tags頁面中所有的下載鏈接
? ? ? ? ? ? download_links = soup.find_all('a', href=lambda href: href and (href.endswith('.zip') or href.endswith('.tar.gz')))
? ? ? ? ? ? #print("所有鏈接:" + download_links)
? ? ? ? ? ? if len(download_links) == 0:
? ? ? ? ? ? ? ? print("當前項目tags下無發布版本")
? ? ? ? ? ? ? ? break
?? ??? ??? ?#分析每一個鏈接
? ? ? ? ? ? for link in download_links:
? ? ? ? ? ? ? ? #print("Found download link:", link['href'])
? ? ? ? ? ? ? ? file_name = os.path.basename(link['href']) ?#file_name是壓縮包名字
? ? ? ? ? ? ? ? if os.path.isfile(file_name):
? ? ? ? ? ? ? ? ? ? print("該項目版本已經下載,略過!")
? ? ? ? ? ? ? ? ? ? continue
? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? if download_file("https://github.com/" + link['href'], file_name): ?#拼接為完整下載鏈接后下載文件
? ? ? ? ? ? ? ? ? ? #print(f"確認文件已下載完成")
? ? ? ? ? ? ? ? ? ? renamefile(Name,file_name)
?? ??? ??? ??? ??? ?
? ? ? ? ? ? ? ? ? ? lastfilename, _ = os.path.splitext(file_name) ?#lastfilename 是去掉.zip或.gz后的文件名
? ? ? ? ? ? ? ? ? ? if lastfilename.endswith('.tar'): ?#如果后面還有tar后綴
? ? ? ? ? ? ? ? ? ? ? ? lastfilename, _ = os.path.splitext(lastfilename) ?#lastfilename 文件名稱,實際上版本號
? ? ? ? ? ? ? ? ? ? #項目名稱,tags url、版本號和壓縮包文件名,是否找到發布包寫入文件。1表示有發布包
? ? ? ? ? ? ? ? ? ? with open(save_file_name, 'a', newline='') as file:
? ? ? ? ? ? ? ? ? ? ? ? writer = csv.writer(file)
? ? ? ? ? ? ? ? ? ? ? ? writer.writerow([Name, url, lastfilename, file_name,'1'])
? ? ? ? ? ? ? ? ? ? num = num + 1 ?#當前下載文件數加1
? ? ? ? ? ? ? ? else:
? ? ? ? ? ? ? ? ? ? headers = {
? ? ? ? ? ? ? ? ? ? ? ? 'User-Agent': 'Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11',
? ? ? ? ? ? ? ? ? ? ? ? 'Authorization': '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',
? ? ? ? ? ? ? ? ? ? ? ? 'Content-Type': 'text/html',
? ? ? ? ? ? ? ? ? ? ? ? 'Accept': 'application/json'
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? print(f"更換header重新下載...")
? ? ? ? ? ? ? ? ? ? download_file("https://github.com/" + link['href'], file_name) ?#拼接為完整下載鏈接后下載文件
? ? ? ? ? ? ? ? if ?num == 20:
? ? ? ? ? ? ? ? ? ? continue
? ? ? ? ? ? ? ? #如果有多頁,每頁返回10個版本,一共20個壓縮包,如果不到20表示版本頁面不到一頁, ? ?
? ? ? ? ? ? if num < 20:
? ? ? ? ? ? ? ? break
? ? ? ? #user_input = input("請輸入任意內容,按 Enter 鍵結束程序:")
? ? ? ? #if user_input:
? ? ? ? # ? ?print("用戶輸入了ctrl+C:", user_input)
? ? ? ? #
?? ??? ?#break
? ? ? ? else:
? ? ? ? ? ? print(f'頁面返回不是200時,返回狀態碼是: {response.status_code}')
? ? ? ? ? ? continue
?
# 測試程序
if __name__ == "__main__":
? ? if len(sys.argv) != 3:
? ? ? ? print("請提供兩個參數:第一個參數是項目列表;第二個需要創新的文件用來保存該項目版本信息")
? ? ? ? sys.exit(1)
? ??
? ? file_name = sys.argv[1]
? ? if not file_name.endswith('.csv'):
? ? ? ? print("Please provide a CSV file.")
? ? ? ? sys.exit(1)
? ? if os.path.exists(file_name):
? ? ? ? print("打開文件,開始分析...")?
? ? else:
? ? ? ? print("輸入文件不存在,請確認")
? ? ? ? sys.exit(1)
?? ?
? ? save_file_name = sys.argv[2]
? ? if not save_file_name.endswith('.csv'):
? ? ? ? print("Please provide a CSV save file.")
? ? ? ? sys.exit(1)
? ? #如果保存文件不存在,則創建文件,添加表頭
? ? if os.path.exists(save_file_name):
? ? ? ? print("保存文件已經存在,會在文件后面追加數據")?
? ? else:
? ? ? ? with open(save_file_name, 'a', newline='') as file:
? ? ? ? ? ? writer = csv.writer(file)
? ? ? ? ? ? writer.writerow(fieldnames)
? ? #Name,html_urls = get_html_url_with_tags(file_name)
? ? #for url in html_urls:
? ? Name = ''
? ? with open(file_name, 'r', newline='') as file:
? ? ? ? reader = csv.DictReader(file)
? ? ? ? for row in reader:
? ? ? ? ? ? ifcheck = row.get('ifcheck')
? ? ? ? ? ? Name = row.get('Name')
? ? ? ? ? ? html_url = row.get('HTML URL')
? ? ? ? ? ? if ifcheck == '0' and html_url:
? ? ? ? ? ? ? ? new_html_url = html_url + "/tags"
? ? ? ? ? ? ? ? analyze_download_links(Name,new_html_url)
? ? ? ? ? ? ? ? update_ifcheck_value(file_name, Name)
? ? ? ? ? ? #user_input = input("您的輸入中斷了下載,按Ctrl+C鍵結束程序,其它鍵繼續下載")
? ? ? ? ? ? #if user_input:
? ? ? ? ? ? ?# ? print("用戶輸入了:", user_input)
? ? ? ? ? ? ?# ? break
? ? print("檢測完成!")?
————————————————————————————————