Chromedriver通过network日志获取response.body

爬虫webdriver 技术分享

概述:本博文主要分享chromdriver日志获取response.body 方法和其他selenium简单使用。由于之前Linux Chromedriver chrome-browser安装环境复杂、问题较多加上以前站点反爬策略较为简单,很少用性能相对较弱的webdriver去投入到爬虫的项目中。现在由于webdriver开发环境越来越方便简单,加上站点反爬力度和JavaScript复杂程度越来越高。所以webdriver技术值得仔细研究并投入生产项目。

环境:python3,selenium, chromedriver

官方文档:

https://selenium-python.readthedocs.io/
https://sites.google.com/a/chromium.org/chromedriver/ https://chromedevtools.github.io/devtools-protocol/tot/Network/

第一章 安装环境。

Windows和mac 相对简单。分享centos7 安装

  1. 安装selenium

pip3 install selenium

  1. 安装chrome-browser
wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm
yum install ./google-chrome-stable_current_x86_64.rpm

安装完成后会显示当前版本。
我安装的版本是87.0.4280.66-1

  1. 安装chromedriver(千万与chrome-browser版本对应)

http://chromedriver.storage.googleapis.com/这个页面找对应版本的下载地址
wget http://chromedriver.storage.googleapis.com/87.0.4280.20/chromedriver_linux64.zip
解压unzip chromedriver_linux64.zip
mv chromedriver /usr/bin即可

第二章 爬虫基本使用

爬虫常用操作:

1、设置UserAgent

2、频繁更换ip。文档和网上其他博客启动chromedriver 设置代理的方法很多,但是更换代理需要退出重新启动,极大浪费时间和系统资源。 难以满足爬虫更换代理需求。所以咱们要知道频繁更换Chromedrive代理的方法。

Demo:

from selenium import webdriver
from selenium.webdriver.common.desired_capabilities  import DesiredCapabilities
from selenium.webdriver.common.proxy import Proxy
from selenium.webdriver.common.proxy import ProxyType
option = webdriver.ChromeOptions()
option.add_argument('--no-sandbox')
option.add_argument('--headless')
#设置user-agent
option.add_argument('user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:55.0) Gecko/20100101 Firefox/55.0')
caps = DesiredCapabilities.CHROME
driver = webdriver.Chrome(options=option,desired_capabilities=caps)
p = '222.185.28.38:32374'
proxy = Proxy(
    {
        'proxyType':ProxyType.MANUAL,
        'httpProxy':'{}'.format(p),
    }
    )
proxy.add_to_capabilities(caps)
driver.start_session(caps)
driver.get('http://httpbin.org/get')
print(driver.page_source)
#更换proxy后 重新执行proxy.add_to_capabilities(caps) 和 driver.start_session(caps)即可。不用重启driver更新代理成功。

由于是浏览器模式, headers等信息不必过多干预。换proxy和UA 是爬虫核心需求。其他基本操作详见官方文档https://python-selenium-zh.readthedocs.io/zh_CN/ 或者博客https://cloud.tencent.com/developer/article/1067129

第三章 webdriver爬虫进阶使用

以前不喜欢使用webdriver还有一个原因, 一些ajax请求,还有一些通过开发者模式可以找到API返回json格式的请求,通过page_source解析效率低且字段没有API全。所以我就开始想,既然浏览器开发者模式可以对network进行分析,webdriver应该也有相应的功能。

如果Chromedrive能对network进行监控,直接拦截response 拿到response.body.那样岂不是非常爽?

最开始想到的方案A: 通过mitmproxy等网络中间人方式对请求进行监控和拦截。

但是方案A有以下几个弊端难以成气候。

1、webdriver需要设置mitmproxy服务为代理,爬虫正常代理逻辑就需要增加二次代理逻辑。 增加很多成本和异常。难以实现流畅的更换代理逻辑。
2、webdriver 拦截的request、和response难以和浏览器url和相应操作关联,数据交互逻辑混乱。
3、webdriver需要安装mitm第三方公共证书。但是现在很多站点有ssl ping等反爬逻辑。 网络中间人等方案难以突破反爬逻辑。 导致webdriver请求失败。

方案A诸多问题, 果断放弃。

最理想的方案是直接通过webdriver进行日志分析,拿到response。

然后就开始调研webdriver日志性能分析方法。 经过一些列调研尝试,终于可以拿到webdriver的network日志了。但是结果有些失落。因为日志没有response.body。日志结构如下:

{
    "message":{
        "method":"Network.responseReceived",
        "params":{
            "frameId":"35489C704F5D95B9D01BD99C00BA3AFA",
            "loaderId":"BA3FE8EAD2BB30562343F3940AEB8CBC",
            "requestId":"BA3FE8EAD2BB30562343F3940AEB8CBC",
            "response":{
                "connectionId":12,
                "connectionReused":false,
                "encodedDataLength":230,
                "fromDiskCache":false,
                "fromPrefetchCache":false,
                "fromServiceWorker":false,
                "headers":{
                    "Access-Control-Allow-Credentials":"true",
                    "Access-Control-Allow-Origin":"*",
                    "Connection":"keep-alive",
                    "Content-Length":"571",
                    "Content-Type":"application/json",
                    "Date":"Tue, 24 Nov 2020 01:59:03 GMT",
                    "Server":"gunicorn/19.9.0"
                },
                "headersText":"HTTP/1.1 200 OK
Date: Tue, 24 Nov 2020 01:59:03 GMT
Content-Type: application/json
Content-Length: 571
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

",
                "mimeType":"application/json",
                "protocol":"http/1.1",
                "remoteIPAddress":"3.211.1.78",
                "remotePort":80,
                "requestHeaders":{
                    "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
                    "Accept-Encoding":"gzip, deflate",
                    "Accept-Language":"en-US",
                    "Connection":"keep-alive",
                    "Host":"httpbin.org",
                    "Upgrade-Insecure-Requests":"1",
                    "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:55.0) Gecko/20100101 Firefox/55.0"
                },
                "requestHeadersText":"GET /get HTTP/1.1
Host: httpbin.org
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:55.0) Gecko/20100101 Firefox/55.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US
",
                "responseTime":1606183143651.414,
                "securityState":"insecure",
                "status":200,
                "statusText":"OK",
                "timing":{
                    "connectEnd":282.386,
                    "connectStart":47.972,
                    "dnsEnd":47.972,
                    "dnsStart":46.748,
                    "proxyEnd":-1,
                    "proxyStart":-1,
                    "pushEnd":0,
                    "pushStart":0,
                    "receiveHeadersEnd":519.16,
                    "requestTime":43961290.624483,
                    "sendEnd":282.616,
                    "sendStart":282.547,
                    "sslEnd":-1,
                    "sslStart":-1,
                    "workerFetchStart":-1,
                    "workerReady":-1,
                    "workerRespondWithSettled":-1,
                    "workerStart":-1
                },
                "url":"http://httpbin.org/get"
            },
            "timestamp":43961291.145069,
            "type":"Document"
        }
    },
    "webview":"35489C704F5D95B9D01BD99C00BA3AFA"
}

内容很丰富,但是没有我们最想要的。 接下来,上重点。我这个小白都能想到的方法果然已经有人在做了。很庆幸找到了完美的解决办法。Chrome devtool 的 Network.getResponseBody 方法,就是我们想要的。https://chromedevtools.github.io/devtools-protocol/tot/Network/


Network.getResponseBody #

Returns content served for the given request.
parameters

requestId
    RequestId

    Identifier of the network request to get content for.

Return Object

body
    string

    Response body.
base64Encoded
    boolean

    True, if content was sent as base64.


具体思路:通过Chromedrive获取到network日志。network 的response日志类型Network.responseReceived,里面有个参数 requestId , chrome devtool Network.getResponseBody方法可以通过requestId 获取到response。 话不多说,上代码尝试!

import time
import json
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities  import DesiredCapabilities
from selenium.webdriver.common.proxy import Proxy
from selenium.webdriver.common.proxy import ProxyType

caps = DesiredCapabilities.CHROME
caps['loggingPrefs'] = {
    'browser':'ALL',
    'performance':'ALL',
}
caps['perfLoggingPrefs'] = {
    'enableNetwork' : True,
    'enablePage' : False,
    'enableTimeline' : False
    }

option = webdriver.ChromeOptions()
option.add_argument('--no-sandbox')
option.add_argument('--headless')
option.add_argument("--disable-extensions")
option.add_argument("--allow-running-insecure-content")
option.add_argument("--ignore-certificate-errors")
option.add_argument("--disable-single-click-autofill")
option.add_argument("--disable-autofill-keyboard-accessory-view[8]")
option.add_argument("--disable-full-form-autofill-ios")
option.add_argument('user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:55.0) Gecko/20100101 Firefox/55.0')
option.add_experimental_option('w3c',False)
option.add_experimental_option('perfLoggingPrefs',{
    'enableNetwork':True,
    'enablePage':False,
})
driver = webdriver.Chrome(options=option,desired_capabilities=caps)
driver.get('http://httpbin.org/get')
for typelog in driver.log_types:
    perfs = driver.get_log(typelog)
    for row in perfs:
        log_data = row
        log_json = json.loads(log_data['message'])
        log = log_json['message']
        if log['method'] == 'Network.responseReceived':
            requestId = log['params']['requestId']
            try:
                response_body = driver.execute_cdp_cmd('Network.getResponseBody',{'requestId': requestId})
                print(response_body['body'])

            except:
                print('response.body is null')

至此,监控Chromedrive network 完美解决! 你可以使用Chromedrive随心所欲更换代理,可以设置UserAgent,官方有非常多完善的日常使用浏览器的操作方法,并且现在你可以控制webdriver的network直接解析。相信我们使用webdriver开发爬虫会如虎添翼。

第四章 webdriver和requests 灵活切换。

掌握以上一些技巧之后虽然还算可以使用, 但是还是效率问题!webdriver固然可以满足需求,但是操作流程可能十分麻烦, 举个场景, 我们有个很长的列表页,需要对详情页挨个采集,详情页还需要模拟点击其他位置。这样的操作对webdriver简直是噩梦。 请求的操作逻辑太长了。 此时我们需要考虑的能封装request的还尽量使用代码请求为上策。我们可以选择携带cookies使用requests。也可以使用requestium模块transfer_driver_cookies_to_session()和transfer_session_cookies_to_driver()提升效率。话不多说,上代码:
#coding=utf-8
import time,sys,os
from requestium import Session, Keys
s = Session(webdriver_path='/Users/lib/Documents/chromedriver',
            browser='chrome',
            default_timeout=15,
            webdriver_options={'arguments': ['disable-gpu',"--user-agent=Mozilla/5.0 (iPod; U; CPU iPhone OS 2_1 like Mac OS X; ja-jp) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.1.1 Mobile/5F137 Safari/525.20"]})
            #webdriver_options={'arguments': ['headless','--proxy-server=http://user:pwd@ip2.hahado.cn:40275']})
            #webdriver_options={'arguments': ['headless','--proxy-server=http://116.11.254.37:80']})
#url = 'https://icanhazip.com/'
url = 'https://www.che300.com/pinggu/v9c9m30614r2016-06g2.8'
url = 'https://magi.com/search?q=%E6%96%B0%E5%86%A0'
url = 'http://m.baidu.com/s?word=%E6%88%BF%E5%B1%B1%E6%96%B0%E7%9B%98'
s.driver.get(url)
html =  s.driver.page_source

print (html)
#pic = response.xpath('//img/@src').extract_first()
pic = None
#if pic is not None:
if u'访问太过于频繁,请输入验证码后再次访问' in html:

  #print pic
  code = raw_input('请输入验证码:')
  print ('您输入的验证码是:%s' % code)
  s.driver.find_element_by_xpath("//input[@name='code']").send_keys(code,Keys.ENTER)
  time.sleep(1)
  s.driver.ensure_element_by_xpath("//input[@type='submit']").click()
  #self.s.transfer_driver_cookies_to_session()

#s.transfer_session_cookies_to_driver()
time.sleep(3)
print (s.driver.page_source)
s.driver.quit()

踩过的坑:

1、获取日志过程中如果没有 option.add_experimental_option('w3c',False) 会报错。
2、Network.getResponseBody 在抓取请求的body过程中,如果body为空 程序报错

No resource with given identifier found

记得加异常处理。

3、Centos 服务器 安装好 chrome 和 chromedriver后 截图不能显示中文。 原因:系统默认是英文字符集 ,没有中文语言和字体库。解决办法如下。查看修改服务器字符集[root@192-168-17-194 htms-op-ui-automation-test]# echo $LANG en_US.UTF-8确认系统是否支持中文字符集locale -a |grep CNCentos7修改系统默认字符集vim /etc/locale.conf # LANG="en_US.UTF-8" LANG="zh_CN.UTF-8"安装中文字符集支持yum -y groupinstall "X Window System" yum -y groupinstall chinese-support # Centos7如果报错找不到chinese-support可忽略 yum -y groupinstall Fonts

联系作者:

QQ:739669518
爬虫技术交流群:259788518

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容