最近用selenium和browsermobproxy弄了个爬虫,专门去某个网站爬取pdf文件。虽然该网站没有提供下载文件的功能,但用户在浏览器上预览pdf内容时,浏览器事实上已经下载了pdf了,所以我试着用browsermobproxy在拦截请求阶段把文件给保存下来。
import browsermobproxy
from selenium import webdriver
from selenium.webdriver import chrome
server = browsermobproxy.Server(r'D:\browsermob-proxy-2.1.4\bin\browsermob-proxy.bat', {'port': 9723})
server.start()
browsermob_proxy = server.create_proxy({'trustAllServers': True}) # 设置trustAllServers为true,否则访问https网站报错
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument(f'--proxy-server={browsermob_proxy.proxy}')
chrome_options.add_argument('--ignore-certificate-errors') # 设置--ignore-certificate-errors,否则设置代理后的https网站无法正常访问
driver = webdriver.Chrome(options=chrome_options, service=chrome.service.Service(r'D:\chromedriver.exe'))
以上就是初始化操作,接下来便是操控浏览器,在需要拦截的请求之前,每次都创建一个新的har,如下:
browsermob_proxy.new_har(options={'captureContent': True, 'captureBinaryContent': True})
浏览器加载完pdf后,就可以通过遍历来得到自己想要的东西了,如下:
for entry in browsermob_proxy.har['log']['entries']:
# 具体的逻辑代码
万万没想到,browsermob_proxy.har里的确有拦截到了pdf的报文,但却是字符串文本的乱码报文,写到文件里也不是一个正常pdf文件,根本打不开。
寻找解决办法的过程很曲折,去browsermob-proxy的github页面看介绍,去看browsermob-proxy的源码,终于有了结果。
先说一下我对于browsermob-proxy的理解:
browsermob-proxy是一个java项目,启动后会作为一个代理服务器,浏览器设置它为代理后,我们就可以通过该项目对外开放的一系列REST API来操作了。事实上python里browsermobproxy模块的很多方法源码里都是requests.get和requests.put。
那么问题来了,请看下边的client.py源码:
@property
def har(self):
"""
Gets the HAR that has been recorded
"""
r = requests.get('%s/proxy/%s/har' % (self.host, self.port))
return r.json()
得到的这个har它就是个json字符串,那我想要的pdf文件,也就是非文本型的文件怎么放进去一个json里呢?
browsermob-proxy也有考虑这方面的问题,就是非文本型报文会转为base64字符串,然后才放到json里,这样就没问题了。
那browsermob-proxy怎么判断响应报文是否为非文本型的呢?请看java代码:
/**
* Returns true if the content type string indicates textual content. Currently these are any Content-Types that start with one of the
* following:
*
* text/
* application/x-javascript
* application/javascript
* application/json
* application/xml
* application/xhtml+xml
*
*
* @param contentType contentType string to parse
* @return true if the content type is textual
*/
public static boolean hasTextualContent(String contentType) {
return contentType != null &&
(contentType.startsWith("text/") ||
contentType.startsWith("application/x-javascript") ||
contentType.startsWith("application/javascript") ||
contentType.startsWith("application/json") ||
contentType.startsWith("application/xml") ||
contentType.startsWith("application/xhtml+xml")
);
}
居然是靠报文头的Content-Type字段来判断的。众所周知,Content-Type是最不可信的东西,这东西有时候甚至可以想怎么设置就怎么设置,对报文实体毫无影响。恰好,我想要爬取的pdf就设置为application/javascript,判断为文本型,然后按照默认编码变为字符串乱码了。
上面扯了些没用的,以下进入正题,解决办法:
在拦截时修改pdf的Content-Type头
server = browsermobproxy.Server(r'D:\browsermob-proxy-2.1.4\bin\browsermob-proxy.bat', {'port': 9723})
server.start()
browsermob_proxy = server.create_proxy({'trustAllServers': True}) # 设置trustAllServers为true,否则访问https网站报错
browsermob_proxy.response_interceptor('''
if (contents.isText() && contents.getTextContents().startsWith("%PDF")) {
response.headers().set("Content-Type", "application/pdf");
}
''')
-----------------------------分割线------------------------------
最近发现python里seleniumwire也能实现我这里拦截pdf的功能,不用browsermob-proxy。当然啦,browsermob-proxy可不只是用来跟selenium配合使用的,它应该跟fiddler这些代理服务一样,也可以用来监控其它的东西。