中国采购招标网数据采集

中国采购招标网数据采集

 LiangChengDeYe     2020-12-13   9169 words    & views

中国采购招标网数据采集

URL ,通过爬虫去请求该网站会返回521状态码,需要带着特定cookie去访问,此cookie又是动态变化,如果想要持续采集就得破解此cookie生成规则。

站点反爬分析

通过Fiddler抓包分析,可以看出它的请求顺序。

  1. 首次发起请求,返回状态码为521,返回第一段加密cookie,携带第一段加密的cookie去请求会返回第二个521状态码,会返回第二段加密cookie,然后携带第一段和第二段cookie去请求页面,才返回正常状态码,通过观察第二段加密cookie有时效性,一会就过期,破解步骤见下面。

原始请求

点击并拖拽以移动

  1. 第一段加密cookie具体内容:整个script中包含两部分内容,(1):通过js生成的第一段加密cookie;(2):第二个加密cookie页面的跳转,这部分破解比较简单,借助python的execjs包去模拟执行js,然后返回加密cookie即可。
获取请求的set_cookie,返回内容为:__jsluid_h=7cad7d7e20bf090ce259b49c85542372(此参数在后续请求均需携带);执行js返回的document.cookie内容:__jsl_clearance=1603345157.389 -1 HI%2B0JxwfexVK0OVf0WEG4FxNW3A%3D,一开始以为这就是破解成功的cookie,谁知道注意观察后返回200状态码的请求cookie格式为:XXX 0 XXXX 而此次请求返回中间部分为-1,只有请求第二个加密js后才能获取正确cookie。

第一段加密cookie第一段加密cookie

点击并拖拽以移动

代码片段:

def first_cookie_decode(base_url):
    """
    破解首次加密
    :param base_url: 原始请求URL
    :return: 浏览器请求头,首次破解加密字段一
    """
    response = requests.get(base_url, headers=HEADERS, timeout=(8, 8))
    if response.status_code == 521:
        cookies = response.cookies
        str_js_cookie = response.text.replace("<script>document.", "").replace(
            ";location.href=location.pathname+location.search</script>", "")
        print("0. 待破解字段==>", str_js_cookie)  # 替换掉页面跳转部分JS
        # 获取加密字段内容
        js_result = execjs.eval(str_js_cookie).split(";")[0]  # 执行加密JS
        print("1. 待破解加密字段一==>", js_result)
        cookies_text = ';'.join(['='.join(item) for item in cookies.items()])
        print("2. 加密字段一==>", cookies_text)  # 此字段可连续使用
        HEADERS['cookie'] = cookies_text + "; " + js_result
    else:
        print("状态不为521,可直接使用-first_cookie_decode")
    return HEADERS, cookies_text

点击并拖拽以移动

  1. 第二段加密cookie具体内容:

首先整个页面的JS是通过不同编码搞成了乱码,无法直接查看JS具体内容,但是通过第三方js执行工具执行发现执行后总是会跳转页面,因此考虑会和第一段浏览器跳转一样,最后发现了go()方法,而且看到go()方法调用的参数bts为我们需要的破解参数,破解与未破解区别如下:

未破解: 1603071828.236 0 nuN 4FZAvCZ1Ba7ZTI%2B%2FKxzFJY%3D
执行第二段JS破解后的cookie:1603071828.236 0 nuN vi 4FZAvCZ1Ba7ZTI%2B%2FKxzFJY%3D

发现原来的bts参数后面部分多了两位字母,长达300多行的js就干了一件事,通过入参与浏览器的请求头(很重要)生成了加密的两位字母并拼接到了第二段加密cookie中。第二段js本想通过本地execjs执行,但是发现js代码片段中包含对浏览器配置的获取,因此本地无法执行,则考虑selenium去尝试,结果本机的chrome86.0版本死活无法加载该js,目前有什么配置暂不清楚,一直降级发现chrome73.0版本支持该js执行并加载。关于此部分js内容破解,通过阅读该js源码发现有部分:“document[_0x197c(‘0x35’, ‘g%4U’) + ‘ie’]”和:“location[_0x197c(‘0x49’, ‘5@s1’)]”,很明显location控制跳转,我们只要对document部分内容做破解即可,document内容(这是它执行自己内部函数的关键):

_0x1d288_0x197c(‘0x61’, ‘(zd#’) + ‘J’ + ‘J’](_0x1d288_0x197c(‘0x86’, ‘&91$’) + ‘V’, _0x1c6092[0x0]) + (_0x197c(‘0x5a’, ‘kUvB’) + _0x197c(‘0x2b’, ‘T@q(‘) + ‘=’) + _0x54977a[‘vt’], _0x197c(‘0x77’, ‘3Z!!’) + _0x197c(‘0x2a’, ‘Qi@k’) + ‘\x20/’);

第二段加密cookie的JS内容第二段加密cookie的JS内容

点击并拖拽以移动

具体细节第二段加密cookie具体细节

点击并拖拽以移动

  1. 破解流程:
  • 发起首次请求返回521状态,获取本次请求的set_cookie,并获取加密js内容:__jsluid_h=7cad7d7e20bf090ce259b49c85542372;
  • 携带__jsluid_h和set_cookie发起第二次请求:__jsl_clearance=1603345157.389 -1 HI%2B0JxwfexVK0OVf0WEG4FxNW3A%3D:获取正确的第二部分加密JS内容;
  • 通过第二部分加密js内容__jsl_clearance,使用selenium执行返回正确的__jsl_clearance=1603345157.389 0 HI%BV2B0JxwfexVK0OVf0WEG4FxNW3A%3D;
  • 携带__jsluid_h、__jsl_clearance去再次请求,返回正确状态码:200及真实页面内容。(整个过程必须使用同一浏览器,否则加密参数不一致)
  1. 详细代码
#!/usr/bin/python3  
# encoding: utf-8  
""" 
@version: v1.0 
@author: W_H_J 
@license: Apache Licence  
@contact: 415900617@qq.com 
@software: PyCharm 
@file: cookieDecodeJs.py 
@time: 2020/10/19 21:32 
@describe: 中国采购招标网COOKIE解密JS破解
http://shanxi.chinabidding.cc/
"""
import sys
import os
import time

from requests.adapters import HTTPAdapter
from selenium import webdriver
from selenium.webdriver import ChromeOptions
from pyquery import PyQuery as pq
import execjs
import requests_html
sys.path.append(os.path.abspath(os.path.dirname(__file__) + '/' + '..'))
sys.path.append("..")

SESSION = requests_html.HTMLSession()
SESSION.mount('http://', HTTPAdapter(max_retries=6))
SESSION.mount('https://', HTTPAdapter(max_retries=6))

HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',
    'Host': 'shanxi.chinabidding.cc'
}


def first_cookie_decode(base_url):
    """
    破解首次加密
    :param base_url: 原始请求URL
    :return: 浏览器请求头,首次破解加密字段一
    """
    response = SESSION.get(base_url, headers=HEADERS, timeout=(8, 8))
    if response.status_code == 521:
        cookies = response.cookies
        str_js_cookie = response.text.replace("<script>document.", "").replace(
            ";location.href=location.pathname+location.search</script>", "")
        print("0. 待破解字段==>", str_js_cookie)
        # 获取加密字段内容
        js_result = execjs.eval(str_js_cookie).split(";")[0]
        print("1. 待破解加密字段一==>", js_result)
        cookies_text = ';'.join(['='.join(item) for item in cookies.items()])
        print("2. 加密字段一==>", cookies_text)  # 此字段可连续使用
        HEADERS['cookie'] = cookies_text + "; " + js_result
    else:
        print("状态不为521,可直接使用-first_cookie_decode")
    return HEADERS, cookies_text


def second_cookie_decode(base_url):
    """
    破解第二段加密cookie
    :param base_url: 原始请求URL
    :return: 破解完的加密js代码片段,通过内部api请求模拟获取最终加密结果,此结果可多次使用
    """
    HEADERS_F, cookies_first_decode = first_cookie_decode(base_url)  # 浏览器请求头,第一个加密字段
    response = SESSION.get(base_url, headers=HEADERS_F, timeout=(8, 8))
    if response.status_code == 521:
        text_second_521 = response.text
        js_cookie = text_second_521[text_second_521.find('go({"bts":[') + 12:text_second_521.find('"],"chars')].split(
            '","')
        print("3. 待破解加密字段二==>", js_cookie[0] + js_cookie[1])
        # 二次破解cookie加密字段
        str_base = text_second_521
        print("4. 待破解字段三==>", str_base)
        str_ie_cookie = str_base.replace(" ", "")[
                        str_base.replace(" ", "").find("'ie']=") + 6:str_base.replace(" ", "").find("location[")]
        print("5. 破解的加密JS内容==>", str_ie_cookie)
        str_cookie_temp = '''var cookies={}\nreturn cookies;'''.format(str_ie_cookie)
        str_base_temp = str_base[str_base.rfind(")]);if(") + 4:str_base.rfind("}};go({") + 1]
        str_back_fun = '''var str_back = back();console.log(str_back); setTimeout(function() {document.getElementById('cookieId').innerHTML=str_back;}, 500);'''
        str_js_cookie = str_base.replace(str_base_temp, str_cookie_temp).replace("go({",
                                                                                 "var back_cookie = go({").replace(
            "})</script>", "}); return back_cookie;}" + str_back_fun).replace("<script>", "function back(){")
        with open("./statisticalDataSpider/spider/business/cookieDecode/static/cookie_js.js", 'w', encoding="utf-8") as f:
            f.write(str_js_cookie)  # 保存二次加密的js到本地然后通过本地起一个服务,使用selenium执行,获取正确的加密cookie
    else:
        print("状态不为521,可直接使用")
    return cookies_first_decode, response.status_code


def third_cookie_decode(status_code):
    """
    进行第二次解密参数破解
    :return: __jsl_clearance=1603095437|0|IIfaCCOxqoEKlTN7dqVU%2Blb2ypw%3D
    """
    # 需要chrome及chromdriver均需使用版本73.0
    if status_code == 521:
        options = ChromeOptions()
        options.add_experimental_option("prefs", {"profile.managed_default_content_settings.images": 2})  # 不加载图片,加快访问速度
        options.add_argument('--disable-gpu')
        options.add_argument('--no-sandbox')
        # 手动指定使用的浏览器位置
        # options.binary_location = r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
        options.add_argument('user-agent="{}"'.format(HEADERS['User-Agent']))
        options.add_experimental_option('excludeSwitches', ['enable-automation'])  # 此步骤很重要,设置为开发者模式,防止被各大网站识别出来使用了Selenium
        chrome_path = r"./statisticalDataSpider/spider/business/cookieDecode/static/chromedriver.exe"
        browser = webdriver.Chrome(executable_path=chrome_path,options=options)
        browser.get('http://127.0.0.1:9093/cookie')  #自己起了一个本地服务,让selenium执行js
        time.sleep(5)
        page = browser.page_source
        doc = pq(page)
        js_cookie = doc("#cookieId").text()
        browser.close()
        if js_cookie != "undefined" and js_cookie != "默认":
            third_cookie = js_cookie.split(";")[0]
            return third_cookie
        else:
            return None


def get_decode_cookie(base_url):
    """
    最终破解成功的cookie
    :param base_url: 原始URL
    :return:
    """
    cookie_first_decode, status_code = second_cookie_decode(base_url)
    cookie_second_decode = third_cookie_decode(status_code)
    if cookie_second_decode is not None:
        print("6. 破解加密字段一成功==>", cookie_first_decode)
        print("7. 破解加密字段二成功==>", cookie_second_decode)
        HEADERS['cookie'] = cookie_first_decode + "; " + cookie_second_decode
        print("8. 解密后的Headers==>", HEADERS)
        return HEADERS
    else:
        return None


def get_html(base_url):
    headers = get_decode_cookie(base_url)
    html = SESSION.get(base_url, headers=headers, verify=False, timeout=(5, 5))
    print("9. 最终返回状态码==>", html.status_code)
    print("BODY", html.text)
    return html.status_code, html.text


if __name__ == '__main__':
    url = 'http://shanxi.chinabidding.cc/lists.html?page=4&zz=city_119&keyword=%E8%A5%BF%E5%AE%89%20%E8%A5%BF%E5%AE%89&pid=9&city=120&time=1'
    get_html(url)

点击并拖拽以移动