爬虫流程总结

Posted 初级码农12138

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了爬虫流程总结相关的知识,希望对你有一定的参考价值。

爬虫—流程总结

请求库:

一、requests

安装第三方库: requests;

导入第三方库:import requests

1.请求网络数据: requests.get(请求地址)
response = requests.get('https://game.gtimg.cn/images/lol/act/img/js/heroList/hero_list.js')
2.设置解码方式(乱码的是需要设置 - 一定要在获取请求结果之前设置)
response.encoding = 'utf-8'
# 编码方式和解码方式必须相同
3.获取请求结果
1)获取请求结果对应的文本数据 - 爬网页------>> response.text
2)获取二进制格式的请求结果 - 下载图片、视频、音频------>> response.content
3)获取请求结果json转换的结果 - json接口------>> response.json()

二、selenium

第三方库: selenium

导入使用: from selenium.webdriver import Chrome (谷歌浏览器)

获取标签对象: from selenium.webdriver.common.by import By

1.创建浏览器对象
b = Chrome()
2.打开网页(需要爬那个页面的数据,就打开那个页面对应的网页地址)
b.get('https://movie.douban.com/top250?start=0&filter=')
3.获取网页源代码(注意:不管以什么样的方式更新了界面内容,page_source的内容也会更新)
print(b.page_source)        # 获取豆瓣电影top250的网页源代码

解析

解析库:bs4,lxml

bs4:

bs4(beautifulsoup4),它基于css选择器的网页解析器(css选择器: day2-csv和Bs4)

------>安装的时候装beautifulsoup4,使用的时候用bs4

安装库: beautifulsoup4

导入: from bs4 import BeautifulSoup

1.根据网页源代码创建soup对象: BeautifulSoup(网页源代码, ‘lxml’)
f = open('files/data.html', encoding='utf-8')  # 打开files文件夹中的html文件data.html
soup = BeautifulSoup(f.read(), 'lxml')    # 括号里面f.read()可以是任何html或xml数据
f.close()
2. 获取标签(css选择器直接获取网页标签)

soup对象.select(css选择器) - 获取整个网页中选择器选中的所有标签,返回值是一个列表,列表中的元素是标签对象(找不到返回空列表)

soup对象.select_one(css选择器) - 获取整个网页中选择器选中的第一个标签,返回值是标签对象(找不到返回None)

标签对象.select(css选择器) - 获取指定标签中css选择器选中的所有标签

标签对象.select_one(css选择器) - 获取指定标签中css选择器选中的第一个标签

# 获取整个网页中的P标签返回一个列表
result = soup.select('p')

# 获取整个网页中的第一个标签,返回值是标签对象----->也就是整个网页中的第一个<p></p>标签
result = soup.select_one('p')

# 获取整个网页中<div></div>标签下的所有后代<p></p>标签
result = soup.select('div p')
# 获取box1中的所有标签  
box1 = soup.select_one('#box1')   # 此时是将网页中id='box1'标签下的所有标签看作一个整体(类似于一个盒子)

result = box1.select('p')   # 获取box1下的所有<p></p>标签

p1 = soup.select_one('span>p') # 获取整个网页中<span></span>标签下的第一个<p></p>标签
a1 = box1.select_one('a')  # 获取box1中的第一个<a></a>标签
3.获取标签内容和标签属性

标签对象.text - 获取标签内容

标签对象.attrs[属性名] - 获取标签指定属性的值

print(a1.text)      # '我是超链接3'---a1标签在网页中对应的内容
print(a1.attrs['href'])     # 'https://www.baidu.com'---获取a1标签中'href'属性对应的属性值

selenium获取标签:

获取标签对象导入: from selenium.webdriver.common.by import By

selenium获取标签得创建浏览器对象

浏览器对象.b.find_element(获取方式, 数据) — 返回符合条件的第一个标签,结果是标签对象
浏览器对象.b.find_elements(获取方式, 数据) — 返回符合条件的所有标签,结果是列表,列表中的元素是标签对象

1.获取方式:

By.ID - 通过ID属性值获取标签
By.CLASS_NAME - 通过class属性值获取标签
By.CSS_SELECTOR - 通过css选择器获取标签
By.LINK_TEXT - 通过a标签的标签内容获取标签
By.PARTIAL_LINK_TEXT - 通过a标签的标签内容获取标签

2.操作标签

1)输入框输入内容:输入框对应的标签.send_keys(‘内容’)
2)点击标签:标签对象.click()

3.获取标签内容和标签属性

find_element仅仅能够获取元素,不能够直接获取其中的数据

获取属性值时通过定位获取的标签对象的get_attribute函数,传入属性名,来获取属性的值--------->不能直接用标签对象.attrs[属性名]

标签对象.text—> 即element.text

------------获取标签内容

标签对象.get_attribute(“属性名”) —>即:element.get_attribute(“属性名”)

-----------获取标签指定属性的值

解析数据方式:

1.正则-------> day1-正则

2.bs4(css选择器,依赖lxml库)------> 如上解析库bs4部分

3.XPath------> day5-XPath

请求头headers

我们访问一个网页的时候,点击进去就是一个完整的浏览过程。当我们想要获取网页数据的时候,就要跟这种浏览过程一样,不然网页会把我们阻拦在外面。header就是帮助我们完成这样浏览过程的一个工具。给爬虫加一个header请求头,是常规的反爬的方法之一,相当于我们进去这个网页的服务器本身,假装自己本身在浏览网站信息。

发送请求

添加header: a. 浏览器伪装(user-agent)、b.免密登录(cookie)、 c. 设置代理(proxies)

a. 浏览器伪装(user-agent)
import requests
headers = 
    # 发送请求的时候在请求头中添加cookie值
    'cookie': 'bid=58Gyjz_NAcA; ll="118318"; douban-fav-remind=1; viewed="36164018_36221918"; ap_v=0,6.0',
    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36'

# 可以自己在网页中查---检查---网络--->>
response = requests.get('https://movie.douban.com/top250', headers=headers)
b.免密登录(cookie) <-----为了爬网页时跳过登录界面

requests

# requests自动登录步骤
# 第一步:人工对需要自动登录网页进行登录
# 第二步:获取这个网站登录后的cookie信息
# 第三步:发送请求的时候在请求头中添加cookie值
# 如上面代码部分

selenium

selenium获取Cookie:

from selenium.webdriver import Chrome
# 1.创建浏览器打开需要自动登录的网页
b = Chrome()
b.get('https://www.taobao.com')

# 2.留足够长的时候,人工完成登录(必须得保证b指向的窗口的网页中能看到登录以后的信息)
input('是否已经完成登录:')  # ---此时在跳出来的网页上进行登录以获取Cookie,登陆后回车让后面的代码继续运行

# 3.获取登录成功后的cookie信息,保存到本地文件
result = b.get_cookies()
with open('files/taobao.txt', 'w', encoding='utf-8') as f:
    f.write(str(result))

selenium使用Cookie:

# 接着获取Cookie的代码部分---使用Cookie
# 获取本地保存的cookie
with open('files/taobao.txt', encoding='utf-8') as f:
    result = eval(f.read())
    
# 添加cookie
for x in result:
    b.add_cookie(x)

# 重新打开网页
b.get('https://www.taobao.com')  # 已免登录
input('end:')
c. 设置代理(proxies) <------解决IP被封

requests:

import requests

headers = 
    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36'

# 创建代理
proxies = 
    'https': '116.140.52.224:4513'

# 使用代理ip发送请求
res = requests.get('https://movie.douban.com/top250?start=0&filter=', headers=headers, proxies=proxies)
print(res.text)

selenium

from selenium.webdriver import Chrome, ChromeOptions

options = ChromeOptions()
# 设置代理
options.add_argument('--proxy-server=http://180.127.245.154:4515')

b = Chrome(options=options)
b.get('https://movie.douban.com/top250?start=0&filter=')

瑞数维普期刊JS逆向详细流程及4000字爬虫总结

前言

我所用的方法基于浏览器环境的,非硬解(头秃ing😶),文章较长,建议收藏。

这是我第一次接触瑞数加密,比较难,不过学到的东西也是挺多的,也是因为我第一次解瑞数,所以文章写得比较详细甚至是啰嗦,这篇文章大致是以我逆向的思路去写的,应该适合像我这样从未接触过瑞数的朋友。

这次逆向总结,估计会写3到4篇文章。

  1. 接口签名的生成与获取
  2. cookie的生成与获取
  3. 基于浏览器环境的爬虫如何部署?
  4. 关于本次瑞数解密的总结

本文中也会有一些调试技巧夹在其中,如有问题或更好的建议欢迎提出!

本文以维普期刊的高级检索接口作为示例,地址:http://qikan.cqvip.com/Qikan/Search/Advance?from=index


正文开始。


过debugger

定时器debugger

拿到网站后,一如既往的直接打开审查工具,在这一步直接被debugger卡住了。

image-20210524191531900

这是一个定时器无限debugger,如果两次时间差大于100,那么就会一直让我们处于debugger状态。

当遇到这种反调试的手法时,可在进入debugger状态后,在console中输入以下代码,以此跳过。

for (var i = 1; i < 99999; i++)window.clearInterval(i);

参考:爬虫漫游指南:瑞数的反调试陷阱

死循环debugger

过了这一步后,当我回到网页,又会直接进入debugger状态。这个debugger是在一个判断下,这个比较简单我们直接右键选着**“永不在此处暂停”**。

image-20210524192101416

**注意:**以上步骤都是在谷歌浏览器中调试的,使用火狐情况会不一致,建议使用谷歌浏览器。

这些if判断都是在一个while(1)循环内,使用火狐会一直在循环内,谷歌只要设置了不在此处暂停就没事,具体为啥不知道。

分析搜索接口

输入关键词,点击检索。很容易找到请求路径为SearchList?...的链接就是数据接口。

查看这个请求,需要搜索参数searchParamModel和签名G5tA5iQ4

image-20210524193554482

查看这个请求的调用栈

image-20210524195302725

这里可以先去看看window.advSearch这个方法。这里的urldata组合后,并不存在这个字段G5tA5iQ4

所以这个字段的值不是在这里构建,这个字段对应的值就是签名,也是我们必须要解决的。

image-20210524195352293

先这里提前解释下,为什么这里明明没有设置G5tA5iQ4的值,却在请求发送时,含有这个签名。

原因很简单,XMLHttpRequestsend方法被修改了。下图是两者的对比。

image-20210526153930804

签名在何处生成?

进入第一个函数

image-20210524193236051

在这里打上断点,再去点击检索按钮,进入调试。

image-20210524200135688

注意看这个arguments,这是请求的参数。

由于点击一次检索按钮会有好几个请求,所以这里会有多个请求经过这里。

我们需要调试的是SearchList这个接口,所以只要不是SearchList的接口参数,直接跳过即可。

结合上文的接口分析,searchParamsModel就是搜索参数。

image-20210524200530360

不过,不要忘了。我们的目的是找签名在何处生成。

**小提示:**上文说过XMLHttpRequestsend方法被修改过,实际上,上图的_$hp就是所谓的send方法。

此时查看this

image-20210524200905790

继续往下找,查看_$a5,(变量名每次请求都不同,知道是它就行),展开并查看它的第一个作用域。

image-20210524201405988

可以看到,在此处的_$q3的值就是一个带有签名的链接。

_$q3属于_$M3,那么就可以按Ctrl+F搜索关键字_$M3

注意:_$M3是一个函数,所以你应该找到是类似下面这样的函数

image-20210524201922829

我们得知_$q3作为参数传进了_$M3,所以在这个函数内打上断点,重新调试

当我们进来的时候,查看_$q3,发现_$q3只是请求链接,所以由此可知,签名就是在_$M3中生成并拼接到_$q3上的。

image-20210524202226754

为了方便调试,我们可以将这里的断点换成条件断点,即只调试_$q3==="/Search/SearchList"这种情况。

image-20210524202523045

将内部函数折叠,可以看到_M$3内部只是调用了一下_$Vq函数。

image-20210524202719617

展开_$Vq函数,分析代码。在这个_$Vq函数里面,多折腾几下总能找到签名是何处生成的。

当然也可以用点小技巧,我们知道签名最终会拼接到_$q3上,所以必然存在类似这样的代码:

_$q3 += 
或者
_$q3 +

image-20210524203035177

断点调试到此处,可知签名是调用_$5Q(_$hp, _$M_, _$No)得来的。

查看这个函数的三个参数,可知它们分别是0大小为16的整数数组以及一个undefined

image-20210524203208234

在这里,_$M_是整数数组,我们可以向上寻找,查看_$M_是如何生成的。

可以找出整数数组是将/Search/SearchList作为参数传入某个函数而生成的。

image-20210524203502074

中场休息

分析到这里,我们知道了签名生成的流程如下:

  1. 当用户点击搜索按钮,触发点击事件;
  2. 构建请求对象(请求对象的参数没有签名关键字);
  3. 由于send方法被修改,所以调用send方法时,签名就在这个过程中被生成;

签名代码来源分析

其实你应该发现了,分析了这么久的JS代码,却不知道这大段JS存放在哪里?

像下图这样,JS来源显示为VM+数字的形式,这就说明这些JS代码是后来加载进引擎的。

换句话说就是,这些JS代码并不是存在一个JS文件里的,实际上是通过eval函数将一大堆字符串加载进了内存。

image-20210526154956627

此时就需要寻找以上JS代码是如何加载进内存。

这个也是瑞数加密的一大特色,这些加载JS代码的代码本身就是被混淆的,并且存在于Html页面中。

查看搜索页面源码:view-source:http://qikan.cqvip.com/Qikan/Search/Advance?from=index

**Tips:**如果是加载是空白,那么你需要先正常访问一次搜索页面再查看源代码。这个是Cookie的原因,具体的生成机制及解决办法会在下一章讲解到。

image-20210526161702620

这一行代码的后面,就是一堆JS代码,我们可以将整个网页代码拷贝至本地编辑器。

将html代码格式化后查看。

一开始就加载了一个JS文件,为了调试方便,可以将这个JS文件下载到本地

http://qikan.cqvip.com/NJDrTcXo8msX/leE4DkIasHMb.f22c526.js

image-20210526162149084

查看leE4DkIasHMb.f22c526.js,一堆杂乱的字符,其主要的作用就是为window.$_ts赋值。

image-20210526162420017

html代码引入leE4DkIasHMb.f22c526.js后,紧接着就是一个自调用的被混淆的JS代码。

这段代码的核心作用就是将leE4DkIasHMb.f22c526.js中的杂乱字符串通过特定方式还原为代码并加载进内存。

此时的主要工作就是找到,杂乱字符串变成规则字符串代码的位置。

为什么要这么做,在这里举个简单的例子。


举例时间到!

现在这样一串字符串, Y29uc29sZS5sb2coJ2hpaGloaWhpLi4uJyk=

这很常见,这是通过base64编码后得到的字符串,那么我们可以通过base64解码得到本来的字符串,然后使用eval函数执行即可。

动画1

以上是原本的功能,打印输出了hihihihi...

现在我们需要修改这个代码输出的内容,而这可以通过字符串替换方式实现,就像这样。动画2

那么下面的流程图,我想应该可以理解了。

image-20210526171516534

上方的例子是因为我们知道编码方式是base64,所以可以轻松的将密文转为明文。

对于不常见的加密方式,我们就只有去调试找出明文生成的位置,再加上JS代码本事就是混淆的,所以难度就有所提升。

举例结束。


签名代码在何处加载到内存?

仔细想一想,一段字符串想以js代码的形式加载进内存,必定会使用eval方法。

所以,我们只需要找到哪里使用了调用eval即可。

搜索关键词eval,运气很好可以直接搜索到,下面赋值的操作执行即,_$vo就是eval

image-20210526182806058
此时搜索关键词_$vo,发现有93个匹配项

image-20210526183100317

_$vo作为函数,如果被调用,那么调用的写法可能是这样_$vo()

则搜索关键词_$vo(,匹配项为0个。

image-20210526183242443

函数的调用还有一种函数名.call的方式,所以不妨搜索_$vo.call试试看。

image-20210526183429659

找到一个匹配结果,在这里打上断点,调试过来看看传入的参数是什么。

可见,_$kw的值就是代码字符串了。

image-20210526183540925

我们在eval执行前注入自己的代码即可达成目的。

注入代码

为了大家方便阅读,我将上一步得到的明文代码字符串称为签名代码

虽然这代码还有其他的功能,但对我们来说,只想通过这段代码获取签名,仅此而已。

注入代码是为了让我们可以更方便的获取到签名,最简单的办法就是将签名设置为一个全局变量。

除此之外,为了方便后续调试还可以剔除其中烦人的debugger

设置签名为全局变量

如果有些忘记了签名是如何生成的,可以先翻到第三节回忆回忆。

由于代码的变量是变化的,所以我们不能直接使用replace,而是应该用正则匹配的方式去替换或插入代码。

image-20210526185806409

image-20210526190013277

以上是两次请求签名生成的代码行。

在变化中找不变,_$f_[5]_$A4[5]它们都是取索引值5

所以写出正则如下:

(_\\$[\\w\\d_$]{2}) \\+= _\\$[\\w\\d_$]{2}\\[5\\] \\+ (_\\$[\\w\\d_$]{2}\\([^)]+\\);)

Tips:这是基于格式化的js代码写的正则,实际的签名代码是被压缩的,所以应该把多余的空格删除。

(_\\$[\\w\\d_$]{2})\\+=_\\$[\\w\\d_$]{2}\\[5\\]\\+(_\\$[\\w\\d_$]{2}\\([^)]+\\);)

匹配出这一段后,我们可以在原本的代码后面,再加上一句全局变量赋值。

那么可以写出JS代码

签名代码.replace(/(_\\$[\\w\\d\\$]+)\\+=_\\$[\\w\\d\\$]+\\[5\\]\\+(_\\$[\\w\\d\\$]+\\([^)]+\\);)/gm, `$1="?"+$2window.genUrl=$1;`);

剔除debugger

在最初分析搜索接口时,就遇见了两个debugger

一个是明文显示的,这个比较简单,使用签名代码.replace('debugger', '')剔除即可。

另一个定时器debugger则稍稍有点麻烦。

经过多次调式,可以发现整个代码也是在一个while循环中跑,这是瑞数的一大特色。

image-20210526195421150

且if语句比较的值是没有变化的,都是变量小于256,这为我们注入代码提供了方便。

我们以注入的方式,while内的第一个if语句的上方插入以下代码:

console.log(_$Mx)

这个_$Mx是一直做比较的,通过层层的if else,最终执行某段代码。

当进入调试工具后,只要进入了此循环,就会打印_$Mx

而当进入了定时器debugger,此时循环停止,通过最后输出的数字,就可以找到进入定时器debugger的入口

image-20210526195902790

**Tips:**这一步可能会卡着,稍微等等。

注入后的代码

image-20210526201251851

进入定时器debugger后

image-20210526200611677

最后的数字是388,记着这个388,一路跟着if走就可以看到类似如下代码:

image-20210526201801875

这个代码就是进入定时器debugger的入口,那么我们只需要将这行代码注释或者删除即可

签名代码.replace(/(<389\\){)[^}]+/gm, `$1`);

至此所有的debugger都已去除。

小结

上方的所有代码注入都是在html源码上进行的。

这里先理一理我们的流程:

  1. 请求搜索页面,获得页面html源码
  2. python对html源码进行修改
  3. 将html放入浏览器运行
  4. 调用签名方法获取签名

上方的注入是在html源码中进行的。实际情况是使用python来完成代码注入。

画个图来说明下,即使用Python修改html源码,使得html中的js代码能过将目标代码注入到签名代码中

image-20210526204401228

代码注入示例:

# -*- coding: utf-8 -*-
"""
Created on 2021/5/24 17:15
---------
@summary: 注入代码
1. 去除定时无限debugger
2. 去除死循环debugger
3. 插入searchList方法,用于生成签名
4. 将得到的签名提升至全局变量,可通过 `genUrl` 访问
---------
@author: mkdir700
@email:  mkdir700@gmail.com
"""
import re


def purify_html():
    with open("raw.html", encoding="utf-8") as f:
        text = f.read()
    
    r = re.findall("_\\$[\\w\\d\\$]{2}\\(79,_\\$[\\w\\d\\$]+\\);", text)[0]
    var_name = re.findall("_\\$[\\w\\d\\$]{2}\\(79,(_\\$[\\w\\d\\$]+)\\);", r)[0]
    
    pp = """%(var_name)s = %(var_name)s.replace(/(_\\$[\\w\\d\\$]+)\\+=_\\$[\\w\\d\\$]+\\[5\\]\\+(_\\$[\\w\\d\\$]+\\([^)]+\\);)/gm, `$1="?"+$2window.genUrl=$1;`);%(var_name)s = %(var_name)s.replace(/(<389\\){)[^}]+/gm, `$1`);%(var_name)s = %(var_name)s.replace("debugger", "");""" % {
        'var_name': var_name}
    
    result = text.replace(r, pp + r)
    result = result.replace("/NJDrTcXo8msX/leE4DkIasHMb.f22c526.js",
                            "http://qikan.cqvip.com/NJDrTcXo8msX/leE4DkIasHMb.f22c526.js")
    result = re.sub(r"(/dist)", "http://qikan.cqvip.com/dist", result)
    result = result.replace("</body>",
                            '<script>searchList=function(a){$.ajax({url:"/Search/SearchList",type:"post",dataType:"html",data:{searchParamModel:a},beforeSend:function(){loadding()},complete:function(){loaddingClose()},success:function(){console.log("请求成功")},error:function(){loaddingClose()}})};</script></body>')
    # print(result)
    
    with open('pure.html', "w", encoding="utf-8") as f:
        f.write(result)
    
    print("HTML页面代码注入完成")

**Tips:**签名的触发机制是发送请求,所以注入了一个ajax请求,可供我们手动调用。

签名测试

  1. 打开搜索页面
  2. 右键查看搜索页面源码
  3. 使用python脚本注入代码,生成新的html文件
  4. 在新的html文件同目录下,启动简单的web服务
python -m http.server 9000
  1. 访问http://localhost:9000/pure.html
  2. 打开审查工具,调用searchList方法,接着访问genUrl变量

  1. 在浏览器中获取cookie
  2. 将cookie和签名带入,测试请求是否成功,发送请求的脚本如下:
# -*- coding: utf-8 -*-
"""
Created on 2021/5/23 16:38
---------
@summary: 
---------
@author: mkdir700
@email:  mkdir700@gmail.com
"""
import requests
payload = "searchParamModel=%7B%22ObjectType%22%3A1%2C%22SearchKeyList%22%3A%5B%7B%22FieldIdentifier%22%3A%22M%22%2C%22SearchKey%22%3A%22%E5%8C%97%E5%A4%A7%22%2C%22PreLogicalOperator%22%3A%22%22%2C%22IsExact%22%3A%220%22%7D%5D%2C%22SearchExpression%22%3A%22%22%2C%22BeginYear%22%3A%22%22%2C%22EndYear%22%3A%22%22%2C%22JournalRange%22%3A%22%22%2C%22DomainRange%22%3A%22%22%2C%22PageSize%22%3A%220%22%2C%22PageNum%22%3A%221%22%2C%22Sort%22%3A%220%22%2C%22ClusterFilter%22%3A%22%22%2C%22SType%22%3A%22%22%2C%22StrIds%22%3A%22%22%2C%22UpdateTimeType%22%3A%22%22%2C%22ClusterUseType%22%3A%22Article%22%2C%22IsNoteHistory%22%3A1%2C%22AdvShowTitle%22%3A%22%E9%A2%98%E5%90%8D%E6%88%96%E5%85%B3%E9%94%AE%E8%AF%8D%3D%E5%8C%97%E5%A4%A7%22%2C%22ObjectId%22%3A%22%22%2C%22ObjectSearchType%22%3A%220%22%2C%22ChineseEnglishExtend%22%3A%220%22%2C%22SynonymExtend%22%3A%220%22%2C%22ShowTotalCount%22%3A%220%22%2C%22AdvTabGuid%22%3A%224361c899-1a1a-2b2b-eefe-cf94a80612f7%22%7D"

cookies = input("请键入cookies:\\r\\n")
genUrl = input("请键入genUrl:\\r\\n")

session = requests.Session()
session.headers = {
    "Accept": "text/html, */*; q=0.01",
    "Accept-Encoding": "gzip, deflate, br",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Cache-Control": "no-cache",
    "Connection": "keep-alive",
    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
    "Cookie": cookies,
    "Host": "qikan.cqvip.com",
    "Origin": "http://qikan.cqvip.com",
    "Pragma": "no-cache",
    "Referer": "http://qikan.cqvip.com/Qikan/Search/Advance?from=index",
    "sec-ch-ua": '" Not A;Brand";v="99", "Chromium";v="90", "Google Chrome";v="90"',
    "sec-ch-ua-mobile": "?0",
    "Sec-Fetch-Dest": "empty",
    "Sec-Fetch-Mode": "cors",
    "Sec-Fetch-Site": "same-origin",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36",
    "X-Requested-With": "XMLHttpRequest",
}

resp = session.request(
    "POST",
    "http://qikan.cqvip.com/Search/SearchList"+genUrl,
    data=payload
)
print(resp.url)
print(resp.status_code)

效果展示

状态码:成功-200,异常-400

动画3

以上是关于爬虫流程总结的主要内容,如果未能解决你的问题,请参考以下文章

Python 小爬虫流程总结

自学PYTHON爬虫阶段总结

Python爬虫教程-28-Selenium 操纵 Chrome

100天精通Python(爬虫篇)——第43天:爬虫入门知识

THS爬虫岗位面试总结

爬虫流程总结