网络爬虫技术总结

少于 1 分钟阅读

如果有兴趣,可以跟我联系交流:这里


学习任何知识,尤其是IT技术这种信息量极大领域的知识领域,最重要的就是要经常做总结,整理出套路,套路就是经验。爬虫方面,套路大概有5类。

方法1:直接接口调用

如果对方网站的api几乎没有做防爬取错失,那么通过浏览器的开发工具直接就可以获取到对方的api地址,直接调用抓取即可。如果对方接口进行了抓取频率控制,那就自己控制一下抓取间隔就行。

如果用python技术栈开发的话,就用requests库模拟接口调用。这里需要注意特定的header,里面可能会有token,signature等等,需要自己整理出来。

方法2:用js解析器执行js代码

上面第一种情况提到的场景毕竟还是少数,绝大多数网站都做了一定的防抓取措施。比如,网站通过加载在页面里的js去向接口发起XHR类型的ajax请求,在调用接口的时候,url的query string或者header里添加了自定义参数,参数的生成方法都在js里,那么你可以选择去看懂js代码,然后用python重写生成参数的代码,最后将这些参数组装好去调用接口。

但是往往没有那么幸运,生成参数的js代码进行了混淆,基本是人无法阅读的,但是你能够定位是使用哪个函数,那么我们就可以采取另一种解决方案,就是,将js代码放到js解释器里去,然后调用该函数,直接拿到返回值,我们拿着这些个返回值去组装请求参数。

如果用python技术栈开发的话,可以使用 PyExecJS,PyV8,js2py,Node.js这几个js解析器去运行js代码。

  • js2py,https://github.com/PiotrDabkowski/Js2Py,一个纯python实现的js解析器,目前还在活跃更新。
  • PyExecJS,https://github.com/doloopwhile/PyExecJS,项目已经停止开发,但是还能用。
  • PyV8,https://github.com/emmetio/pyv8-binaries,是python对google v8 js 引擎的一个封装,很久没更新了,但是还能用。
  • Node.js,https://nodejs.org/zh-cn/,一个基于chrome v8引擎的js运行时,更新活跃。

推荐使用js2py。

方法3:用selenium调用浏览器访问

方法2的场景里面,js代码完全没法利用,根本无法定位使用哪些函数,那么只能搬出selenium了。selenium是一组工具包,通过这组工具包我们可以用程序控制浏览器对目标网站进行访问。selenium有很多语言的binding,自然有python的binding。

用selenium控制浏览器访问到了目标url之后,如果要抓取的内容就在page里,那么直接用selenium提供的网页抽取api把内容直接提取出来即可。但如果你要的内容没有渲染在page里,那么我们还得用下一种方法。

方法4:用selenium+proxy抓取接口调用

方法3场景里已经说过了,对于js发起的ajax请求,如果响应数据没有完全反应了DOM里,那么我们得想办法直接提取到ajax的response。方法就是通过selenium + proxy 控制浏览器进行访问,然后再proxy上我们将ajax response截取过来。

常用的proxy是 browsermob-proxy,https://github.com/lightbody/browsermob-proxy,是用java开发的一个http/https代理。这套方案的原理是

  1. 代码里控制启动 browsermob-proxy 。
  2. 代码里控制selenium启动浏览器,将本地代理设置到browsermob-proxy上。
  3. browsermob-proxy会将访问的request和response写入HAR文件里(http://www.softwareishard.com/blog/har-12-spec/),我们解析该HAR的内容就可以拿到response。

方法5:用selenium+浏览器的performance log

该方法可以认为是方法4的升级版,因为浏览器本身就拿到了response,所以我们只要找到合适的办法,就可以直接拿到response。

具体方法是webdriver(python代码控制浏览器的一个组件)能够让我们给浏览器发送Network.getResponseBody命令得到response。webdriver提供的API文档:https://chromedevtools.github.io/devtools-protocol/tot/Network/

DF12A171-D585-432A-99DB-68E909E4334D

需要我们通过一个叫做requestId的参数才能得到response。

首先,初始化一个浏览器控制实例的时候,要开启{“performance”: “ALL”}

    def __init_driver(self):
        capabilities = DesiredCapabilities.CHROME
        capabilities["goog:loggingPrefs"] = {"performance": "ALL"}  # chromedriver 75+
        option = webdriver.ChromeOptions()
        option.add_argument(r"user-data-dir=./var/chrome-data")
        self.__driver = webdriver.Chrome(desired_capabilities=capabilities, options=option)

然后

def __scrape(self, url):
	self.__driver.get(url)
    time.sleep(3) # 等待页面中的请求完成
    logs = self.__driver.get_log("performance")

logs里面就是页面中所有的请求和响应,我们接下来就是需要遍历里面的每一条数据,找到属于Network.responseReceived类型的,并且request url是我们要抓取的那些数据,从中拿到requestId,然后就可以用Network.getResponseBody拿到response了。

def process_network_event(driver, logs, match_url):
    for entry in logs:
        message = json.loads(entry["message"]).get("message", {})
        method = message.get("method", "")
        is_method_match = method.startswith("Network.responseReceived")
        if not is_method_match:
            continue

        url = message.get("params", {}).get("response", {}).get("url", "")
        if url == "":
            continue
        if not url.startswith(match_url): # 匹配我们想要的url
            continue

        request_id = message.get("params", {}).get("requestId", "")
        if request_id == "":
            continue

        try:
            response_body = driver.execute_cdp_cmd('Network.getResponseBody', {'requestId': request_id})
        except Exception as e:
            print(f"getResponseBody by {request_id} failed: {e}, with message: {message}")
            response_body = None

        if not response_body:
            continue

        json_string = response_body.get("body", "")
        if json_string == "":
            continue
        response = json.loads(json_string)
        return response

还有一个基于 performance log的python模块可以让提取request和response更加容易,selenium-wire · PyPIwkeeling/selenium-wire: Extends Selenium’s Python bindings to give you the ability to inspect requests made by the browser.。该模块更新活跃

综上所述,方法5:用selenium+浏览器的performance log 是无法直接抓接口的情况下最好的解决方案了。

另外,总结几点爬虫项目中常用的技巧

  1. UserAgent要伪装的像一点,并且可以经常更换不同的UserAgent来伪装不同的客户端。
  2. 多准备点Proxy,如果目标网站对IP这块控制的比较严格的话,那我们就频繁更换代理。