Scrapy爬虫抓取PDF后部分元数据未写入CSV问题求助
核心错误原因
PDF链接拼接逻辑错误
你当前在parse_item里用固定的起始页URL(start变量)拼接相对路径的PDF链接,如果你爬到站点其他子页面的相对路径PDF,拼接出来的链接是错误的,会触发404,自然不会写入CSV。
修改方法:把parse_item函数里的base_url = start替换成base_url = response.url,用当前页面的URL来拼接相对路径,才能得到正确的PDF链接。异步并发导致CSV写入冲突
Scrapy默认是异步并发请求的(默认并发数16),多个PDF下载请求会同时调用add_to_csv方法打开同一个CSV文件写入,会出现写入冲突,导致大量数据丢失。
临时验证方法:运行爬虫时加参数关闭并发,scrapy crawl pdfspider -s CONCURRENT_REQUESTS=1,如果写入CSV的条数正常,就可以确认是该问题。
长期解决方法:把CSV写入逻辑迁移到Scrapy自带的Item Pipeline中,Pipeline是单线程串行处理结果的,天然不会出现写入冲突,比你自己实现的写入逻辑更稳定。PDF处理逻辑无异常捕获,任意报错都会中断写入流程
你当前的get_data和isTagged函数没有任何全局异常捕获,只要遇到加密PDF、损坏PDF、缺少元数据字段、编码不兼容的情况,就会直接抛出异常终止当前请求的执行,不会走到写入CSV的步骤,这也是丢数据的常见原因。
修改方法:给两个处理函数加全局异常捕获,遇到错误返回默认值,不要中断流程,代码示例如下:
# 修改后的get_data函数 from pdfminer3.pdfparser import PDFParser from pdfminer3.pdfdocument import PDFDocument from pdfminer.high_level import extract_pages def get_data(file): try: fp = open(file, 'rb') parser = PDFParser(fp) doc = PDFDocument(parser) info = doc.info[0] if doc.info else {} # 用get方法取字段,避免KeyError,解码时忽略错误字符 author = info.get("Author", b"").decode(errors="ignore").strip() title = info.get("Title", b"").decode(errors="ignore").strip() numPages = len(list(extract_pages(file))) return title, author, numPages except Exception as e: print(f"处理PDF {file} 元数据失败:{str(e)}") return ("未知标题", "未知作者", 0)
# 修改后的isTagged函数 from pdfminer3.pdfinterp import PDFResourceManager, PDFPageInterpreter from pdfminer3.pdfdevice import TagExtractor from pdfminer3.pdfpage import PDFPage from io import BytesIO def isTagged(path, password=''): try: rsrcmgr = PDFResourceManager() retstr = BytesIO() device = None try: device = TagExtractor(rsrcmgr, retstr, codec='utf-8') except: try: device = TagExtractor(rsrcmgr, retstr, codec='ascii') except: print(f"PDF {path} 不支持utf-8和ascii编码") return "未知" fp = open(path, 'rb') interpreter = PDFPageInterpreter(rsrcmgr, device) maxpages = 100 caching = True pagenos=set() for page in PDFPage.get_pages(fp, pagenos, maxpages=maxpages, password=password, caching=caching, check_extractable=True): interpreter.process_page(page) contents = retstr.getvalue().decode(errors="ignore") fp.close() device.close() retstr.close() tags = ["<b'Part'", "</b'Sect'", "</b'Art'", "<b'Content'", "<b'Artifact'"] for tag in tags: if tag in contents: return 'tagged' return 'not tagged' except Exception as e: print(f"检测PDF {path} 标签失败:{str(e)}") return "未知"
- 文件名重名覆盖
你现在直接用URL最后一段作为PDF保存的文件名,如果两个不同的PDF文件名相同,后下载的会覆盖先下载的,先下载的PDF处理时要么读取错误,要么读取到的是后面覆盖后的文件,也会导致数据丢失。
修改方法:给文件名增加唯一标识,比如对URL做MD5哈希作为前缀:
# 在save_pdf函数中修改文件名生成逻辑 import hashlib original_file = response.url.split('/')[-1] url_hash = hashlib.md5(response.url.encode()).hexdigest() file = f"{url_hash}_{original_file}" full_path = os.path.join(save_dir, file)
- allowed_domains配置错误导致PDF链接被过滤
你当前的输入提示让用户输入根域名前缀(比如google),但Scrapy的allowed_domains需要填写完整的根域名(比如google.com),否则子域名下的PDF链接会被自动过滤,无法下载。
修改方法:调整输入提示,让用户输入完整的根域名,比如Enter the domain name of the website to be crawled (domain of https://google.com is "google.com"):。
内容的提问来源于stack exchange,提问作者Mat

