Python实现多线程PDF抓取与下载脚本
在我们日常工作和学习中,经常会遇到一个网站上有大量有价值的PDF文档,比如研究报告、产品手册或教程。如果一个一个手动去查找和下载,不仅费时费力,还容易遗漏。这个Python脚本就是专门为解决这个问题而生的。它能自动帮你遍历指定的网站,像一个小侦察兵一样把所有PDF文件的链接都找出来,然后利用多线程技术,同时开启多个下载任务,极大地提升批量下载的效率,帮你把繁琐的任务一键自动化。
下面是一个使用 Python 编写的脚本,可以从指定网站收集所有 PDF 文档链接,并通过多线程机制加速下载。
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
import os
import threading
from queue import Queue
import time
import argparseclass PDFDownloader:def __init__(self, base_url, max_threads=5, download_dir="downloads"):self.base_url = base_urlself.max_threads = max_threadsself.download_dir = download_dirself.pdf_links = set()self.visited_links = set()self.lock = threading.Lock()self.queue = Queue()# 创建下载目录if not os.path.exists(download_dir):os.makedirs(download_dir)def is_valid_url(self, url):"""检查URL是否有效且属于同一域名"""parsed = urlparse(url)base_parsed = urlparse(self.base_url)return bool(parsed.netloc) and parsed.netloc == base_parsed.netlocdef get_absolute_url(self, url):"""将相对URL转换为绝对URL"""return urljoin(self.base_url, url)def extract_pdf_links(self, url):"""从页面提取所有PDF链接"""try:headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'}response = requests.get(url, headers=headers, timeout=10)response.raise_for_status()soup = BeautifulSoup(response.content, 'html.parser')# 查找所有链接for link in soup.find_all('a', href=True):href = link['href']# 检查是否是PDF链接if href.lower().endswith('.pdf'):absolute_url = self.get_absolute_url(href)with self.lock:self.pdf_links.add(absolute_url)# 如果是普通链接,添加到队列中继续爬取else:absolute_url = self.get_absolute_url(href)if self.is_valid_url(absolute_url) and absolute_url not in self.visited_links:self.queue.put(absolute_url)self.visited_links.add(absolute_url)except Exception as e:print(f"Error processing {url}: {str(e)}")def download_pdf(self, url):"""下载PDF文件"""try:# 从URL提取文件名filename = os.path.basename(urlparse(url).path)if not filename:filename = f"document_{int(time.time())}.pdf"filepath = os.path.join(self.download_dir, filename)# 如果文件已存在,跳过下载if os.path.exists(filepath):print(f"File already exists: {filename}")returnprint(f"Downloading: {filename}")headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'}response = requests.get(url, headers=headers, stream=True, timeout=30)response.raise_for_status()# 写入文件with open(filepath, 'wb') as f:for chunk in response.iter_content(chunk_size=8192):if chunk:f.write(chunk)print(f"Downloaded: {filename}")except Exception as e:print(f"Error downloading {url}: {str(e)}")def crawl_worker(self):"""爬取工作线程函数"""while True:url = self.queue.get()if url is None:breakself.extract_pdf_links(url)self.queue.task_done()def download_worker(self):"""下载工作线程函数"""while True:url = self.queue.get()if url is None:breakself.download_pdf(url)self.queue.task_done()def find_all_pdfs(self, max_pages=50):"""查找网站中的所有PDF链接"""print("Starting to crawl for PDF links...")# 初始URL加入队列self.queue.put(self.base_url)self.visited_links.add(self.base_url)# 创建爬取线程threads = []for _ in range(self.max_threads):t = threading.Thread(target=self.crawl_worker)t.start()threads.append(t)# 等待队列完成或达到最大页面数pages_processed = 0while not self.queue.empty() and pages_processed < max_pages:time.sleep(0.1)pages_processed += 1# 停止所有线程for _ in range(self.max_threads):self.queue.put(None)for t in threads:t.join()print(f"Found {len(self.pdf_links)} PDF documents")return self.pdf_linksdef download_all_pdfs(self):"""下载所有找到的PDF文件"""print("Starting download process...")# 将PDF链接加入队列for pdf_url in self.pdf_links:self.queue.put(pdf_url)# 创建下载线程threads = []for _ in range(self.max_threads):t = threading.Thread(target=self.download_worker)t.start()threads.append(t)# 等待所有下载完成self.queue.join()# 停止所有线程for _ in range(self.max_threads):self.queue.put(None)for t in threads:t.join()print("All downloads completed!")def main():parser = argparse.ArgumentParser(description='PDF Downloader from Website')parser.add_argument('url', help='Base URL to start crawling from')parser.add_argument('-t', '--threads', type=int, default=5, help='Number of threads (default: 5)')parser.add_argument('-d', '--directory', default='downloads', help='Download directory (default: downloads)')parser.add_argument('-m', '--max-pages', type=int, default=50, help='Maximum pages to crawl (default: 50)')args = parser.parse_args()# 创建下载器实例downloader = PDFDownloader(base_url=args.url,max_threads=args.threads,download_dir=args.directory)# 查找所有PDF链接pdf_links = downloader.find_all_pdfs(max_pages=args.max_pages)if pdf_links:# 下载所有PDF文件downloader.download_all_pdfs()else:print("No PDF documents found on the website.")if __name__ == "__main__":main()
使用说明
1、安装必要的依赖库:
pip install requests beautifulsoup4
2、运行脚本:
python pdf_downloader.py https://example.com -t 10 -d my_pdfs -m 100
参数说明:
url
: 要爬取的网站URL(必需)-t
,--threads
: 线程数量(默认:5)-d
,--directory
: 下载目录(默认:downloads)-m
,--max-pages
: 最大爬取页面数(默认:50)
功能特点
多线程爬取:使用多线程同时爬取多个页面,提高效率
多线程下载:使用多线程同时下载多个PDF文件,加速下载过程
相对URL处理:自动将相对URL转换为绝对URL
域名限制:只爬取同一域名下的链接
重复检测:避免重复下载同一文件
错误处理:完善的异常处理机制
进度显示:显示下载进度和状态
注意事项
1、请确保遵守目标网站的robots.txt规则
2、尊重网站服务器负载,适当调整线程数量
3、仅用于合法和道德的目的
4、某些网站可能有反爬虫机制,可能需要额外处理
这个脚本提供了基本功能,您可以根据需要进一步扩展和优化。
使用这个脚本非常简单,你只需要在命令行里指定要抓取的网址,它就会自动开始工作。所有下载好的PDF文件都会整齐地保存在你指定的文件夹中。不过需要注意的是,请务必尊重网站的规定,不要过度频繁请求而给对方服务器造成压力。合理设置线程数量,并确保你的使用方式是合法合规的。希望这个工具能成为你的得力助手,帮你从重复劳动中解放出来,高效地获取所需的资料。