Python如何从动态加载PDF的URL下载文件并适配Chrome无头模式
问题描述
注意:本问题和Stack Overflow上其他同类问题存在明显差异。
原因是目标URL:https://webice.ongc.co.in/pay_adv?TRACKNO=8262#不会直接返回PDF文件,而是会发起多个后续请求,其中某一个请求的响应才是目标PDF文件。
需求是为URL的查询参数TRACKNO传入变量值,通过Python实现对应PDF文件的下载保存。
目前已通过Selenium实现功能,但开启Chrome浏览器无头模式后代码就会失效,需要代码能在无头模式下正常运行。现有代码如下:
import requests from urllib3.exceptions import InsecureRequestWarning from selenium import webdriver from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By import time def extract_url(driver): advice_requests = driver.execute_script("var performance = window.performance || window.mozPerformance || window.msPerformance || window.webkitPerformance || {}; var network = performance.getEntries() || {}; return network;") print(advice_requests) for request in advice_requests: if(request.get('initiatorType',"") == 'object' and request.get('entryType',"") == 'resource'): link_split = request['name'].split('-') if(link_split[-1] == 'filedownload=X'): print("..... Successful") return request['name'] print("..... Failed") def save_advice(advice_url,tracking_num): requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning) response = requests.get(advice_url,verify=False) with open(f'{tracking_num}.pdf', 'wb') as f: f.write(response.content) def get_payment_advice(tracking_nums): options = webdriver.ChromeOptions() # options.add_argument('headless') # 无头模式下无法运行,因此注释 driver = webdriver.Chrome(options=options) for num in tracking_nums: print(num,end=" ") driver.get(f'https://webice.ongc.co.in/pay_adv?TRACKNO={num}#') try: WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, 'ls-highlight-domref'))) time.sleep(0.1) advice_url = extract_url(driver) save_advice(advice_url,num) except: pass driver.quit() get_payment_advice(['8262'])
现有逻辑是在extract_url函数中通过获取浏览器发起的所有网络调用,逐一解析筛选出正确的PDF请求地址,但该逻辑在无头模式下无法生效。
询问是否有更合理的实现方式,或者现有代码如何修复适配无头模式运行。
解决方案
失效原因
Chrome旧版无头模式(使用--headless参数)的浏览器行为和普通模式存在差异,资源加载的性能日志统计规则不一致,导致performance.getEntries()无法捕获到PDF文件的请求记录。
方案1:切换新版无头模式(最小代码改动)
Chrome 112及以上版本推出了新版无头模式,行为和普通Chrome完全一致,仅需修改无头模式配置参数即可:
将原代码中注释的无头模式配置替换为:
options.add_argument('--headless=new') options.add_argument('--disable-gpu') options.add_argument('--no-sandbox')
同时注意原代码最后一行存在语法错误,get_payment_advice['8262']要改为get_payment_advice(['8262']),修改后即可正常运行。
方案2:使用CDP监听网络请求(稳定性更高)
如果不想依赖performanceAPI,可以通过Selenium调用Chrome DevTools协议直接监听所有网络请求,不会出现漏抓的情况,代码可靠性更高:
import requests from urllib3.exceptions import InsecureRequestWarning from selenium import webdriver from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By import time def save_advice(advice_url,tracking_num): requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning) response = requests.get(advice_url,verify=False) with open(f'{tracking_num}.pdf', 'wb') as f: f.write(response.content) def get_payment_advice(tracking_nums): options = webdriver.ChromeOptions() options.add_argument('--headless=new') options.add_argument('--disable-gpu') options.add_argument('--no-sandbox') driver = webdriver.Chrome(options=options) # 启用网络监听 driver.execute_cdp_cmd('Network.enable', {}) for num in tracking_nums: print(num,end=" ") pdf_url = None # 注册请求拦截器捕获PDF地址 def request_interceptor(request): nonlocal pdf_url if 'filedownload=X' in request['request']['url']: pdf_url = request['request']['url'] driver.request_interceptor = request_interceptor driver.get(f'https://webice.ongc.co.in/pay_adv?TRACKNO={num}#') try: WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, 'ls-highlight-domref'))) # 预留足够时间捕获请求 time.sleep(0.5) if pdf_url: print("..... Successful") save_advice(pdf_url,num) else: print("..... Failed") except Exception as e: print(f"..... Error: {str(e)}") driver.quit() get_payment_advice(['8262'])
方案3:纯requests实现(运行效率更高)
如果不想依赖Selenium,可以逆向站点的请求流程,直接用requests模拟携带Cookie、请求头调用接口拿到PDF地址,运行效率更高。该方案需要先梳理站点的请求校验规则,适合请求逻辑固定的场景。
内容的提问来源于stack exchange,提问作者dracarys

