本文目录导读:

我来为您创建一个定时检查并清理无效链接的脚本,这个脚本可以检查文件中的链接是否有效,并自动清理无效的链接。
基础版:检查单个文件中的链接
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
定时检查并清理无效链接脚本
"""
import os
import re
import json
import requests
import time
import logging
from datetime import datetime, timedelta
from urllib.parse import urlparse
from concurrent.futures import ThreadPoolExecutor, as_completed
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('link_checker.log', encoding='utf-8'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
class LinkChecker:
def __init__(self, config_file='config.json'):
"""初始化链接检查器"""
self.config = self.load_config(config_file)
self.valid_links = []
self.invalid_links = []
self.timeout = self.config.get('timeout', 5)
self.retry_times = self.config.get('retry_times', 2)
self.max_workers = self.config.get('max_workers', 10)
def load_config(self, config_file):
"""加载配置文件"""
default_config = {
'files_to_check': ['README.md', 'links.txt'], # 要检查的文件
'backup_dir': 'backup', # 备份目录
'cleanup_mode': 'comment', # 'comment' 或 'delete'
'timeout': 5, # 请求超时时间
'retry_times': 2, # 重试次数
'max_workers': 10, # 并发线程数
'check_interval': 3600, # 检查间隔(秒)
'report_file': 'link_report.json', # 报告文件
'link_patterns': [ # 支持多种链接格式
r'https?://[^\s<>"\'()]+\.[^\s<>"\'()]+',
r'https?://[^\s<>"\'()\[\]]+'
]
}
try:
if os.path.exists(config_file):
with open(config_file, 'r', encoding='utf-8') as f:
loaded_config = json.load(f)
default_config.update(loaded_config)
logger.info(f"配置文件 {config_file} 加载成功")
else:
self.save_config(config_file, default_config)
logger.info(f"已创建默认配置文件 {config_file}")
except Exception as e:
logger.error(f"加载配置文件失败: {e}")
return default_config
def save_config(self, config_file, config):
"""保存配置文件"""
with open(config_file, 'w', encoding='utf-8') as f:
json.dump(config, f, indent=4, ensure_ascii=False)
def extract_links(self, file_path):
"""从文件中提取所有链接"""
links = []
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
for pattern in self.config['link_patterns']:
found_links = re.findall(pattern, content)
links.extend(found_links)
# 去重
links = list(set(links))
logger.info(f"从 {file_path} 中提取到 {len(links)} 个链接")
return links
except Exception as e:
logger.error(f"提取链接失败 {file_path}: {e}")
return []
def check_link(self, url):
"""检查单个链接是否有效"""
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
for attempt in range(self.retry_times + 1):
try:
response = requests.get(
url,
headers=headers,
timeout=self.timeout,
allow_redirects=True
)
if response.status_code == 200:
return url, True, "OK"
elif response.status_code == 403:
return url, True, "Forbidden (可能有效)"
elif response.status_code == 404:
return url, False, f"404 Not Found"
else:
return url, False, f"HTTP {response.status_code}"
except requests.ConnectionError:
if attempt < self.retry_times:
time.sleep(1)
continue
return url, False, "Connection Error"
except requests.Timeout:
if attempt < self.retry_times:
time.sleep(1)
continue
return url, False, "Timeout"
except Exception as e:
if attempt < self.retry_times:
time.sleep(1)
continue
return url, False, str(e)
return url, False, "Max retries exceeded"
def check_all_links(self, file_path):
"""检查文件中的所有链接"""
links = self.extract_links(file_path)
if not links:
logger.warning(f"文件 {file_path} 中没有找到链接")
return [], []
self.valid_links = []
self.invalid_links = []
logger.info(f"开始检查 {len(links)} 个链接...")
with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
future_to_url = {executor.submit(self.check_link, url): url for url in links}
for future in as_completed(future_to_url):
url, is_valid, message = future.result()
if is_valid:
self.valid_links.append(url)
logger.info(f"✓ {url[:50]}... - {message}")
else:
self.invalid_links.append({'url': url, 'reason': message})
logger.warning(f"✗ {url[:50]}... - {message}")
return self.valid_links, self.invalid_links
def backup_file(self, file_path):
"""备份文件"""
if not os.path.exists(self.config['backup_dir']):
os.makedirs(self.config['backup_dir'])
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
filename = os.path.basename(file_path)
backup_path = os.path.join(
self.config['backup_dir'],
f"{filename}.{timestamp}.bak"
)
try:
import shutil
shutil.copy2(file_path, backup_path)
logger.info(f"已备份文件到 {backup_path}")
return True
except Exception as e:
logger.error(f"备份失败: {e}")
return False
def cleanup_links(self, file_path, invalid_links):
"""清理无效链接"""
if not invalid_links:
logger.info("没有发现无效链接,无需清理")
return
# 创建备份
self.backup_file(file_path)
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
cleanup_mode = self.config.get('cleanup_mode', 'comment')
invalid_urls = [link['url'] for link in invalid_links]
for url in invalid_urls:
if cleanup_mode == 'delete':
# 删除包含无效链接的整行
lines = content.split('\n')
content = '\n'.join([
line for line in lines
if url not in line
])
logger.info(f"已删除包含 {url[:50]}... 的行")
else:
# 注释掉包含无效链接的行
lines = content.split('\n')
new_lines = []
for line in lines:
if url in line and not line.strip().startswith('#'):
new_lines.append(f"# INVALID LINK: {line}")
else:
new_lines.append(line)
content = '\n'.join(new_lines)
logger.info(f"已注释包含 {url[:50]}... 的行")
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
logger.info(f"清理完成,已处理 {len(invalid_urls)} 个无效链接")
except Exception as e:
logger.error(f"清理链接失败: {e}")
def generate_report(self, file_path):
"""生成检查报告"""
report = {
'timestamp': datetime.now().isoformat(),
'file_checked': file_path,
'total_links': len(self.valid_links) + len(self.invalid_links),
'valid_links': len(self.valid_links),
'invalid_links': len(self.invalid_links),
'valid_urls': self.valid_links,
'invalid_urls': self.invalid_links,
'cleanup_mode': self.config['cleanup_mode']
}
report_file = self.config['report_file']
try:
with open(report_file, 'w', encoding='utf-8') as f:
json.dump(report, f, indent=2, ensure_ascii=False)
logger.info(f"报告已保存到 {report_file}")
except Exception as e:
logger.error(f"保存报告失败: {e}")
return report
def run_check(self, file_path=None):
"""运行链接检查"""
if file_path is None:
file_paths = self.config['files_to_check']
else:
file_paths = [file_path]
for fp in file_paths:
if not os.path.exists(fp):
logger.warning(f"文件 {fp} 不存在,跳过")
continue
logger.info(f"="*50)
logger.info(f"开始检查文件: {fp}")
valid_links, invalid_links = self.check_all_links(fp)
if invalid_links:
logger.info(f"\n发现 {len(invalid_links)} 个无效链接:")
for link in invalid_links:
logger.info(f" {link['url'][:60]}... - {link['reason']}")
# 清理无效链接
self.cleanup_links(fp, invalid_links)
else:
logger.info("所有链接看起来都有效!")
# 生成报告
self.generate_report(fp)
def run_scheduled(self):
"""定时运行检查"""
logger.info(f"启动定时链接检查,间隔: {self.config['check_interval']}秒")
while True:
try:
self.run_check()
next_check = datetime.now() + timedelta(seconds=self.config['check_interval'])
logger.info(f"下次检查时间: {next_check.strftime('%Y-%m-%d %H:%M:%S')}")
time.sleep(self.config['check_interval'])
except KeyboardInterrupt:
logger.info("用户中断,停止检查")
break
except Exception as e:
logger.error(f"检查过程出错: {e}")
time.sleep(60) # 出错后等待一分钟再试
def main():
"""主函数"""
import argparse
parser = argparse.ArgumentParser(description='定时检查并清理无效链接')
parser.add_argument('--config', '-c', default='config.json', help='配置文件路径')
parser.add_argument('--file', '-f', help='要检查的特定文件')
parser.add_argument('--once', '-o', action='store_true', help='只运行一次检查')
parser.add_argument('--mode', '-m', choices=['comment', 'delete'], help='清理模式')
args = parser.parse_args()
checker = LinkChecker(args.config)
if args.mode:
checker.config['cleanup_mode'] = args.mode
if args.once:
checker.run_check(args.file)
else:
checker.run_scheduled()
if __name__ == '__main__':
main()
使用说明
安装依赖
pip install requests
创建配置文件 config.json
{
"files_to_check": ["README.md", "links.txt", "docs/*.md"],
"backup_dir": "backup",
"cleanup_mode": "comment",
"timeout": 5,
"retry_times": 2,
"max_workers": 10,
"check_interval": 3600,
"report_file": "link_report.json",
"link_patterns": [
"https?://[^\\s<>\"'()]+\\.[^\\s<>\"'()]+",
"https?://[^\\s<>\"'()\\[\\]]+"
]
}
运行脚本
# 单次运行 python link_checker.py --once # 定时运行(默认每小时检查一次) python link_checker.py # 检查特定文件 python link_checker.py --file README.md --once # 设置清理模式为删除 python link_checker.py --mode delete --once # 使用自定义配置 python link_checker.py --config my_config.json
高级功能
添加链接白名单
def should_skip(self, url):
"""检查链接是否应该跳过"""
skip_patterns = [
'localhost',
'127.0.0.1',
'example.com',
]
for pattern in skip_patterns:
if pattern in url:
return True
return False
增强的链接检测
def extract_all_links(self, file_path):
"""从文件中提取所有类型的链接"""
import markdown
from bs4 import BeautifulSoup
# 读取文件
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 提取 Markdown 链接
md_links = re.findall(r'\[([^\]]+)\]\(([^\)]+)\)', content)
# 提取 HTML 链接
soup = BeautifulSoup(markdown.markdown(content), 'html.parser')
html_links = [a.get('href') for a in soup.find_all('a') if a.get('href')]
return list(set(md_links + html_links))
定时任务调度
# 使用 systemd 或 crontab 实现定时运行 # 添加到 crontab # 0 */2 * * * /usr/bin/python3 /path/to/link_checker.py --once
这个脚本提供了完整的链接检查和清理功能,可以根据需要调整配置和功能。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播