avatar


1.发送请求获取响应

爬虫

其实,爬虫能做的事情,我们用鼠标键盘也能做。我们打开浏览器,用鼠标点开一个又一个网页,然后再用键盘把网页的我们想要的内容一个一个的敲到Word文档中。
而所谓的爬虫,不过是程序去模拟客户端(主要指浏览器)发送网络请求,接收请求响应,按照一定的规则,解析响应,提取数据,保存数据。
如图就是爬虫的工作流程
爬虫工作流程

我们从第一步开始"发送请求获取响应"。

HTTP

那么,怎么去发送请求,获取响应呢?

在2012年伦敦奥运的开幕式上,第六章《弗兰克和琼恩说:感谢蒂姆》,弗兰克是指男孩,琼恩是指女孩,而蒂姆是指蒂姆·伯纳斯·李,他在舞台中央,坐在电脑前,敲出了那一句"This is for Everyone",意为把万维网(World Wide Web)献给所有人。他是英国人的骄傲,并在那一刻接受来自全世界的感谢。


蒂姆·伯纳斯·李为万维网奠定了三大基石

  1. HTTP:超文本传输协议(HyperText Transfer Protocol)
  2. HTML:超文本标记语言(HyperText Markup Language)
  3. URL:统一资源定位器(Uniform Resource Locator)

我们发送请求获取响应,所依赖的就是HTTP

HTTP的请求过程

HTTP有所谓的三次握手,四次挥手。也有所谓的先去DNS服务器,请求域名对应的IP,再去请求IP。

但是,对于爬虫,我们可以理解成Client客户端发送请求,Server服务端返回响应,就这么简单。
HTTP的请求过程

不过,我们需要注意的是,我们通过爬虫请求到的页面,通常和我们浏览器上看到的页面,是不一样的。

当我们在浏览器上输入一个URL,比如 https://kakawanyifan.com ,浏览器发送一个Request去获取 https://kakawanyifan.com 的Response,服务器把Response返回给浏览器。浏览器分析Response中的HTML,发现其中引用了很多其他文件,比如jpg文件、css文件、js文件等。然后浏览器会自动再次发送Request去获取jpg文件、css文件、js文件等,再进行渲染。这样,才是我们看到的kakawanyifan.com的主页。

所以呢,我们想要的数据,会在:

  1. 当前URL对应的响应中
  2. 其他URL对应的响应中
  3. 需要通过JS进行解析

其他URL对应的响应中
比如,我们博客中的留言数据,并不是直接来源于浏览器窗口的URL。
博客留言

还有些数据通过JS才能得到
比如,大家注意看这个现象。
location.replace
我们明明访问的是 https://kakawanyifan.com/software-engineering/hexo/
结果却自动跳转到了 https://kakawanyifan.com/10100
这是因为浏览器执行了这么一段JS

1
location.replace("https://kakawanyifan.com/10100");

更具体的实现过程,可以参考《基于Hexo的博客搭建:8.重定向》

对于爬虫,我们还需要特别关注的是Request HeadersResponse Headers

Request Headers

首先,Request Headers。
现在,让我们点开Chrome浏览器的开发者工具,同时,访问一个网站:www.baidu.com
baidu.com

这就是一个非常典型的Request Headers,覆盖了大部分我们要讨论的关于Request Headers的内容。

我们依次来讨论。

首行

请求方法和请求协议。
在这里方法是GET,协议是HTTP/1.1。

HOST

请求的域名(或IP地址)。
在这里是 www.baidu.com

Connection

客户端与服务连接类型。
在这里是keep-alive,即Client客户端告诉Server服务端,我支持长连接。
那么,如果服务端支持长连接,则会在Response Headers包含一个Connection:keep-alive,反之,则是Connection:close

Upgrade-Insecure-Requests

升级不安全的请求,意思是会在加载HTTP资源时自动替换成HTTPS的

User-Agent

浏览器名称。是Client客户端告诉Server服务端,是哪一个浏览器在请求。
所以,如果我们担心被反爬虫的话,可以尝试不断的更换User-Agent来反反爬虫,虽然这种雕虫小技,在绝大部分网站前面,几乎没用。

Accept

Client客户端告诉Server服务端,我接收哪些类型的数据。

  1. Accept: */*:表示什么都可以接收。
  2. Accept: text/html:表示客户端希望接收html文本。
  3. Accept: text/html,application/xhtml+xml,application/xml;q=0.9:表示html、xhtml和xml都支持。q的值默认是1,q=0.9则示优先级不高,即Client客户端告诉Server服务端:我优先接收html、xhtml,xml的话…也行吧。
  4. Application:用于传输应用程序数据或者二进制数据。

Accept-Encoding

浏览器可以接收的编码方式。

Accept-Langeuage

浏览器可以接收的语言种类,如en-us指英语,zh-cn指中文,当服务器能够提供一种以上的语言版本时要用到。

Cookie是在浏览器中寄存的小型数据体,它可以记载和服务器相关的用户信息。
我们要模拟登录的话,会用到Cookie。
此外,如果我们担心被反爬虫的话,也可以通过Cookie来反反爬虫。

Referer

表明Client客户端是从该Referer页面发起的请求,即用来跟踪Web请求来自哪个页面,是从什么网站来的等。
有时候我们会看到下图这种现象,这是因为微信做了防盗链。而实现方式就可以根据referer去判断是否是本网站的地址,如果不是,则拒绝。
盗链

Content-Type

这个主要存在于POST请求中,指明该请求的消息体的数据类型。

(在上文的例子中没有出现)

X-Requested-With

XMLHttpRequest,说明是Ajax异步请求。

(在上文的例子中没有出现)

重点

  1. User-Agent
  2. Referer
  3. Cookie

这三个在服务器被用来进行爬虫识别的频率最高,相较于其余的请求头更为重要。
但保不齐有些网站的开发运维,剑走偏锋,会使用一些比较不常见的请求头来进行爬虫的甄别。

Response Headers

我们再看来Response Headers。
Response Headers
主要关注三个地方。

首行

响应状态码,这个是服务端发给客户端的。
常见的有

  1. 200:成功
  2. 4**:客户端错误,请求包含语法错误或无法完成请求。
    比如:403,服务器理解客户的请求,但拒绝处理。404,没有找到。
  3. 5**:服务器错误,服务器在处理请求的过程中发生了错误。
    比如:503,服务器由于维护或者负载过重未能应答。

更多的状态码,可以参考《关于弹幕视频网站的例子:基于Serverless的弹幕视频网站实现方案》的监控部分。

特别注意:
有时候,我们收到某些错误的状态码,不是真的错了,只是被反爬虫了。
有时候,我们收到正确的状态码,也不是真的正确了,服务器已经识别出我们是爬虫了,但是为了让我们疏忽大意,所以照样返回状态码200,但是Response中并没有数据,甚至是假数据。

人与人之间的信任

总之:
所有的状态码都不可信,一切以Response中的数据为准。

Content-Type

这里会有一个字段charset=utf-8,告诉我们如果要decode的时候,应该用哪种字符集。但不是所有的网站的Content-Type都有charset这个字段,甚至有些网站的这个字段并不准。

这个是设置Cookie,所以如果担心被反爬虫的话,可以在下一次发送请求之前,设置Cookie。
但是呢,设置Cookie的方法很多,也可以通过JS来设置Cookie,所以我们不能只关注这个地方。

HTTPS

最后我们讨论HTTPS。
HTTP是超文本传输协议。

  • 超文本:是指超过文本,不仅限于文本;还包括图片、音频、视频等文件。
  • 传输协议:是指使用共用约定的固定格式来传递转换成字符串的超文本内容

那么什么是HTTPS呢?
不是复数!
HTTPS是在HTTP的基础上进行了加密,S代表的是SSL(安全套接字层),所以HTTPS比HTTP更安全。

  • SSL对传输的内容(超文本,也就是请求体或响应体)进行加密

GET请求

说了这么多,现在我们就来发送请求获取响应。从最简单的GET请求开始。

我们通过requests这个包实现,本章所有的请求也都通过这个包实现。这个包的官方介绍是"Python HTTP for Humans.",似乎透露着程序员的幽默。

requests

但就像程序员都喜欢穿格子衬衫,有一个包的官方介绍透露着几乎一样的幽默,Keras,深度学习中的一个包。
keras

GET请求的方法方法是:

1
requests.get()

获取请求头和响应头

现在我们就来打印我们刚刚讨论的Request HeadersResponse Headers

  • 请求头:response.request.headers
  • 响应头:response.headers

示例代码:

1
2
3
4
5
6
7
import requests

response = requests.get("https://kakawanyifan.com")

print(response)
print(response.request.headers)
print(response.headers)

运行结果:

1
2
3
<Response [200]>
{'User-Agent': 'python-requests/2.24.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
{'Server': 'Tengine', 'Content-Type': 'text/html', 'Content-Length': '6224', 'Connection': 'keep-alive', 'Date': 'Sun, 21 Feb 2021 05:46:30 GMT', 'x-oss-request-id': '6031F3B6A50D7734359552DE', 'x-oss-cdn-auth': 'success', 'Accept-Ranges': 'bytes', 'ETag': '"7BEA036E96E3B33C62A0AE1238C230DB"', 'Last-Modified': 'Fri, 19 Feb 2021 18:18:20 GMT', 'x-oss-object-type': 'Normal', 'x-oss-hash-crc64ecma': '9231358451180443004', 'x-oss-storage-class': 'Standard', 'Vary': 'Accept-Encoding', 'Content-MD5': 'e+oDbpbjszxioK4SOMIw2w==', 'x-oss-server-time': '16', 'Via': 'cache44.l2cn2655[366,304-0,H], cache19.l2cn2655[367,0], vcache1.cn2687[373,200-0,H], vcache31.cn2687[404,0]', 'Content-Encoding': 'gzip', 'Ali-Swift-Global-Savetime': '1613839330', 'Age': '0', 'X-Cache': 'HIT TCP_REFRESH_HIT dirn:6:106683182', 'X-Swift-SaveTime': 'Sun, 21 Feb 2021 05:46:30 GMT', 'X-Swift-CacheTime': '3600', 'Timing-Allow-Origin': '*', 'EagleId': 'dde751a116138863899937330e'}

特别的,我们发现request的默认User-Agent

1
'User-Agent': 'python-requests/2.24.0'

这似乎是在很嚣张的告诉我们访问的网站,爬虫来了,来反我啊。
不要这么嚣张,这么嚣张是不好的。我们在写爬虫的时候,一定要把这个换了。

获取响应内容

那么,我们怎么获取响应内容呢?
有两种方法

  1. response.text
  2. response.content

response.text

示例代码:

1
2
3
4
5
import requests

response = requests.get("https://kakawanyifan.com/")
print(type(response.text))
print(response.text)

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
<class 'str'>


【部分运行结果略】

runtime_unit: '天',
runtime: true,
copyright: {"languages":{"author":"作者: Kaka Wan Yifan","link":"链接: ","source":"来源: Kaka Wan Yifan","info":"著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。"}},
ClickShowText: undefined,
medium_zoom: false,

【部分运行结果略】

乱了

出现乱码了。
出现乱码不要慌,这是因为编解码使用的字符集不同造成的。

我们设置一下字符集就够了。
添加一行代码response.encoding = "UTF-8",指定编码格式。
示例代码:

1
2
3
4
5
6
import requests

response = requests.get("https://kakawanyifan.com/")
response.encoding = "UTF-8"
print(type(response.text))
print(response.text)

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
<class 'str'>

【部分运行结果略】

runtime_unit: '天',
runtime: true,
copyright: {"languages":{"author":"作者: Kaka Wan Yifan","link":"链接: ","source":"来源: Kaka Wan Yifan","info":"著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。"}},
ClickShowText: undefined,
medium_zoom: false,
fancybox: false,

【部分运行结果略】

这回不乱了

response.content

我们再来看第二种方法response.content
示例代码:

1
2
3
4
5
import requests

response = requests.get("https://kakawanyifan.com/")
print(type(response.content))
print(response.content)

运行结果:

1
2
3
4
5
6
7
8
<class 'bytes'>
b'<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge">

【部分运行结果略】

runtime_unit: \'\xe5\xa4\xa9\',\n runtime: true,\n copyright: {"languages":{"author":"\xe4\xbd\x9c\xe8\x80\x85: Kaka Wan Yifan","link":"\xe9\x93\xbe\xe6\x8e\xa5: ","source":"\xe6\x9d\xa5\xe6\xba\x90: Kaka Wan Yifan","info":"\xe8\x91\x97\xe4\xbd\x9c\xe6\x9d\x83\xe5\xbd\x92\xe4\xbd\x9c\xe8\x80\x85\xe6\x89\x80\xe6\x9c\x89\xe3\x80\x82\xe5\x95\x86\xe4\xb8\x9a\xe8\xbd\xac\xe8\xbd\xbd\xe8\xaf\xb7\xe8\x81\x94\xe7\xb3\xbb\xe4\xbd\x9c\xe8\x80\x85\xe8\x8e\xb7\xe5\xbe\x97\xe6\x8e\x88\xe6\x9d\x83\xef\xbc\x8c\xe9\x9d\x9e\xe5\x95\x86\xe4\xb8\x9a\xe8\xbd\xac\xe8\xbd\xbd\xe8\xaf\xb7\xe6\xb3\xa8\xe6\x98\x8e\xe5\x87\xba\xe5\xa4\x84\xe3\x80\x82"}},\n ClickShowText: undefined,\n medium_zoom: false,\n fancybox: false

【部分运行结果略】

又是乱码?
出现乱码不要慌,我们来指定编码格式。
示例代码:

1
2
3
4
5
6
import requests

response = requests.get("https://kakawanyifan.com/")
response.encoding = "UTF-8"
print(type(response.content))
print(response.content)

运行结果:

1
2
3
4
5
6
7
8
<class 'bytes'>
b'<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge">

【部分运行结果略】

runtime_unit: \'\xe5\xa4\xa9\',\n runtime: true,\n copyright: {"languages":{"author":"\xe4\xbd\x9c\xe8\x80\x85: Kaka Wan Yifan","link":"\xe9\x93\xbe\xe6\x8e\xa5: ","source":"\xe6\x9d\xa5\xe6\xba\x90: Kaka Wan Yifan","info":"\xe8\x91\x97\xe4\xbd\x9c\xe6\x9d\x83\xe5\xbd\x92\xe4\xbd\x9c\xe8\x80\x85\xe6\x89\x80\xe6\x9c\x89\xe3\x80\x82\xe5\x95\x86\xe4\xb8\x9a\xe8\xbd\xac\xe8\xbd\xbd\xe8\xaf\xb7\xe8\x81\x94\xe7\xb3\xbb\xe4\xbd\x9c\xe8\x80\x85\xe8\x8e\xb7\xe5\xbe\x97\xe6\x8e\x88\xe6\x9d\x83\xef\xbc\x8c\xe9\x9d\x9e\xe5\x95\x86\xe4\xb8\x9a\xe8\xbd\xac\xe8\xbd\xbd\xe8\xaf\xb7\xe6\xb3\xa8\xe6\x98\x8e\xe5\x87\xba\xe5\xa4\x84\xe3\x80\x82"}},\n ClickShowText: undefined,\n medium_zoom: false,\n fancybox: false,

【部分运行结果略】

怎么还是乱码?
一定是打开方式不对!
打开方式不对

我们来看看,在上面的代码中,我们特别的打印了type。
其中response.text的type是str。而response.content的type居然是bytes二进制。
所以我们需要把bytes二进制解码为str,并指定编码格式是"UTF-8"。

示例代码:

1
2
3
4
5
import requests

response = requests.get("https://kakawanyifan.com/")
print(type(response.content))
print(response.content.decode("UTF-8"))

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
<class 'bytes'>
<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Kaka Wan Yifan</title><meta name="description" content=""><meta name="author" content="Kaka Wan Yifan,i@m.kakawanyifan.com"><meta name="copyright" content="Kaka Wan Yifan"><meta name="format-detection" content="telephone=no"><link rel="shortcut icon" href="/img/favicon.ico"><meta http-equiv="Cache-Control" content="no-transform"><meta http-equiv="Cache-Control" content="no-siteapp"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="https://fonts.googleapis.com" crossorigin="crossorigin"/><meta name="google-site-verification" content="SzZHj5G5vHwv9JUmJD-bxmThc7a6YoZAsaNhwcD-BmM"/><meta name="msvalidate.01" content="1DD984606A0A3BC45A692A32685321AB"/><meta name="baidu-site-verification" content="99Pgg3yv8a"/><meta name="360-site-verification" content="273e85da5f4d732881979af78473b941"/><meta name="twitter:card" content="summary"><meta name="twitter:title" content="Kaka Wan Yifan"><meta name="twitter:description" content=""><meta name="twitter:image" content="https://kakawanyifan.com/img/avatar.png"><meta property="og:type" content="website"><meta property="og:title" content="Kaka Wan Yifan"><meta property="og:url" content="https://kakawanyifan.com/"><meta property="og:site_name" content="Kaka Wan Yifan"><meta property="og:description" content=""><meta property="og:image" content="https://kakawanyifan.com/img/avatar.png"><script src="https://cdn.jsdelivr.net/npm/js-cookie/dist/js.cookie.min.js"></script><script>var autoChangeMode = 'false'

【部分运行结果略】

runtime_unit: '天',
runtime: true,
copyright: {"languages":{"author":"作者: Kaka Wan Yifan","link":"链接: ","source":"来源: Kaka Wan Yifan","info":"著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。"}},
ClickShowText: undefined,
medium_zoom: false,

【部分运行结果略】

区别

  • response.text
    • 类型:str
    • 解码字符集:requests模块自动根据response headers中的content-type作出有根据的推测。(在实际爬虫应用中,因为反爬的存在,不一定正确)
  • response.content
    • 类型:bytes
    • 解码字符集:没有指定

response的其它属性和方法

  1. response.url:响应的url。
  2. response.request.url:请求的url。
  3. response.status_code:响应状态码。
  4. response.request._cookies:响应对应请求的cookie;返回cookieJar类型。
  5. response.cookies:响应的cookie(经过了set-cookie动作;返回cookieJar类型。
  6. response.json():自动将json字符串类型的响应内容转换为Python对象(dictlist)。

示例代码:

1
2
3
4
5
6
7
8
9
import requests

response = requests.get("https://kakawanyifan.com")

print(response.url)
print(response.request.url)
print(response.status_code)
print(response.request._cookies)
print(response.cookies)

运行结果:

1
2
3
4
5
https://kakawanyifan.com/
https://kakawanyifan.com/
200
<RequestsCookieJar[]>
<RequestsCookieJar[]>

设置RequestHeaders

在我们讨论获取响应状态码等内容的时候,我们特别的提到了request的默认User-Agent默认是python-requests/2.24.0,并且说不要填python-requests/2.24.0,不要这么嚣张。

不要这么嚣张

还是要设置User-Agent
在哪里设置?
想一想,在这一章开头,我们的User-Agent出现在哪里?Request Headers。

为了模拟浏览器,欺骗服务器,获取和浏览器一致的内容。我们设置headers。
headers的形式是字典。
比如:

1
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"}

直接以参数的形式传给requests.get(),就这么简单。

示例代码:

1
2
3
4
5
6
7
import requests

headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/54.0.2840.99 Safari/537.36"}
response = requests.get("https://baidu.com", headers=headers)
print(response.request.headers)
print(response.content.decode("UTF-8"))

不按套路出牌

发送带参数的GET请求

我们现在要搜索一个东西,比如搜索Python,那么我们是不是要发送一个带参数的请求?
有两种方法发送带参数的请求

  1. 参数以字典的形式,传参给requests.get()
  2. 参数直接写入url的后缀中。

其实在GET请求中,这两种方法请求的URL是一样的。
示例代码:

1
2
3
4
5
6
7
8
9
10
import requests

headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/54.0.2840.99 Safari/537.36"}
params = {'wd': '同济大学'}
response = requests.get("https://www.baidu.com/s", headers=headers, params=params)
print(response.request.url)

response = requests.get("https://www.baidu.com/s?wd=同济大学", headers=headers)
print(response.request.url)

运行结果:

1
2
https://www.baidu.com/s?wd=%E5%90%8C%E6%B5%8E%E5%A4%A7%E5%AD%A6
https://www.baidu.com/s?wd=%E5%90%8C%E6%B5%8E%E5%A4%A7%E5%AD%A6

发送POST请求

除了GET请求,我们还常见的一种请求是POST。

POST表单提交参数

参数:data

方法:

1
response = requests.post("http://www.baidu.com/", data = data,headers=headers)

示例代码:

1
2
3
4
5
6
7
8
import requests

headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/54.0.2840.99 Safari/537.36"}
data = {'query': 'helo'}
response = requests.post("https://fanyi.baidu.com/langdetect", headers=headers, data=data)
print(response.content.decode("UTF-8"))
print(response.request.headers["Content-Type"])

运行结果:

1
2
{"error":0,"msg":"success","lan":"may"}
application/x-www-form-urlencoded

注意:Content-Type字段为application/x-www-form-urlencoded

POST以请求体形式提交参数

参数:json

示例代码:

1
2
3
4
5
6
7
8
import requests

headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/54.0.2840.99 Safari/537.36"}
data = {'query': 'helo'}
response = requests.post("https://fanyi.baidu.com/langdetect", headers=headers, json=data)
print(response.content.decode("UTF-8"))
print(response.request.headers["Content-Type"])

运行结果:

1
2
{"error":0,"msg":"success","lan":"may"}
application/json

POST上传字符串

有时候,需要发送的请求体比较复杂,我们可以把符合格式的数据做成字符串的形式上传,然后headers根据需要自己定义。

示例代码:

1
2
3
4
5
6
7
8
9
import requests

url = "http://192.168.1.122"
xml = "my xml\n"
xml2 = """{"key":"value"}"""
xml3 = xml + xml2
# headers可以根据需要自定义
headers = {'Content-Type': 'application/html'}
r = requests.post(url, headers=headers, data=xml3)

POST上传文件

参数:files

会以表单形式上传文件。

示例代码:

1
2
3
4
5
import requests

url = "http://192.168.1.122"
files = {'file': open('test.txt', 'rb')}
r = requests.post(url, files=files)

使用代理IP

为什么要使用代理IP?
为了防止被反爬虫,让服务器认为不是同一个客户端在请求。

代理IP的分类

使用代理IP,就是穿上马甲。但是呢,不是每一件马甲都有效。
穿上马甲

根据匿名程度

根据代理IP的匿名程度,可以分为下面三类:

透明代理(Transparent Proxy)
透明代理虽然可以直接"隐藏"你的IP地址,但是还是可以查到你是谁。目标服务器接收到的请求头如下:

1
2
3
REMOTE_ADDR = Proxy IP
HTTP_VIA = Proxy IP
HTTP_X_FORWARDED_FOR = Your IP

匿名代理(Anonymous Proxy)
使用匿名代理,别人只能知道你用了代理,无法知道你是谁。目标服务器接收到的请求头如下:

1
2
3
REMOTE_ADDR = proxy IP
HTTP_VIA = proxy IP
HTTP_X_FORWARDED_FOR = proxy IP

高匿代理(Elite proxy或High Anonymity Proxy)
高匿代理让别人根本无法发现你是在用代理,所以是最好的选择。毫无疑问使用高匿代理效果最好。目标服务器接收到的请求头如下:

1
2
3
REMOTE_ADDR = Proxy IP
HTTP_VIA = not determined
HTTP_X_FORWARDED_FOR = not determined

其实呢,这些都是代理技术上的。但除了代理技术,还有司法手段,真正触犯了党纪国法,根本就不存在所谓的"高匿代理"。
这里推荐一个GitHub的Repo:中国爬虫违法违规案例汇总

根据协议

根据网站所使用的协议不同,需要使用相应协议的代理服务。从代理服务请求使用的协议可以分为:

  1. http代理:目标url为http协议
  2. https代理:目标url为https协议
  3. socks隧道代理(例如socks5代理)等。
    socks代理只是简单地传递数据包,不关心是何种应用协议(FTP、HTTP和HTTPS等)。
    socks代理比http、https代理耗时少。
    socks代理可以转发http和https的请求

代理的实现

方法:

1
requests.get("http://www.baidu.com", proxies = proxies)

proxies的形式:字典

例如:

1
2
3
4
proxies = { 
"http": "http://12.34.56.79:9527",
"https": "https://12.34.56.79:9527",
}
  • 如果proxies字典中包含有多个键值对,发送请求时将按照url地址的协议来选择使用相应的代理ip

示例代码:

1
2
3
4
5
6
7
import requests

proxies = {
"http": "http://166.111.50.182:7890"
}
response = requests.get("https://kakawanyifan.com", proxies=proxies)
print(response.content.decode("UTF-8"))

运行结果:

1
2
3
4
5
6
7
8
9
10
11

【部分运行结果略】

runtime_unit: '天',
runtime: true,
copyright: {"languages":{"author":"作者: Kaka Wan Yifan","link":"链接: ","source":"来源: Kaka Wan Yifan","info":"著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。"}},
ClickShowText: undefined,
medium_zoom: false,
fancybox: false,

【部分运行结果略】

cookie的处理

接下来,我们讨论cookie的处理。
在上文中提到,网站经常利用Request Headers中的Cookie字段来做用户访问状态的保持,模拟普通用户的请求。
那么,我们为了欺骗服务器,就人为的在Request Headers中添加Cookie。
有两种方法:

  1. 利用headers参数添加cookie
  2. 利用专门的cookies参数

利用headers参数添加cookie

我们以github.com这个网站为例,cookie可以通过Chrome浏览的开发者工具获取。
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests

headers_without_cookie = {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36'}

headers_with_cookie = {
'cookie': '_octo=GH1.1.920058066.1612922582; tz=Asia%2FShanghai; _device_id=2f0b1ec1e2c816a1f6b6e0cb5ac416a0; has_recent_activity=1; user_session=dSVXhZIHoWFbyksgBhbRS3LPAEmluYDKaOAncCYeJKZeQSod; __Host-user_session_same_site=dSVXhZIHoWFbyksgBhbRS3LPAEmluYDKaOAncCYeJKZeQSod; tz=Asia%2FShanghai; logged_in=yes; dotcom_user=KakaWanYifan; _gh_sess=dOmnqu8Pvxyadv8Ba7Y%2FpvMLi%2BJkJlJZKMXMfGADDOxe7flWRgzH880jF89uxdpCkMVAtFvtlBngQCgkuBr6lq3wduj5Y6LtgXYtTmT7IL9CBIpSSG7COWuKsYxsZmcHcKDGQsEkQO3hRQH4FmGrX1MMBLegGQkLi%2BnOwsARFmir3gMDMa15BMBI3AvX9fDIR3grW%2BS%2FNED2x4o0KIdByEdlYlGsWQyiCwGF3rNGBxpJagJz%2FK%2BhY%2FJhpQef8n2ut2JYEU%2Bjq0UMdesF8BPLw0AbgqE6gg%2FZbOSc4shk5WzK1shd8w2GJOYwncawB1Eo2yg5kN%2BI%2FdnCB%2F0LpvvcjFZC%2FGWs%2Frxys1fxNxjiCmP1qE9oe64jCYW86VJqVm0Hwf%2BQqhlwOznDkGX3ppj5z5nv3iFOER0HBDsMAoOT23V07rQ79tvoNk8BTFD5JTgvPe2%2BKtlfGeIRgGWIKiQGSo3NQM3khnoNCQ1%2FDDuUeC7aCofPRyxPmtNqfRuHwz6%2BgK7UOgv33mExugmADv27sawT865e0yhASpelAS%2BClwWgSAXytIPtMDcXEl4R7ZQUWxc1CnTNT3bBmHXNmDQYZWoY%2BSSGczXrn9diwKyQhnisJJ5FePB64lR8%2FfLD%2FubSuEAjilcLNUyLvcnKqTcTQPRkTwKScTtBYu4oEKznnLeY3oDXtBBMXOoo8nve8bWAQ4ldtwxNQEirbJKjBdNsysZOxbBziEIOOfOg4XxfadR7SIeKBvflsfuK3JLit6avuyfJqtOVJT08CtrgT15b8IC2CrqwRwOGGDB4QOYEby89ZXmTO%2Bi%2FQr80RqqsJB8LB31jkk4htcWuvl%2FjpT4gILPTOp9h%2FK%2F%2FF9p20SBRhwNKcZf0rNirkgxkEsX1YhgY2Uxx0lr%2B4vg2qYxmQpW5bMs49VlwWoyO196HPA%3D%3D--WQPylmKi7hlcvW4X--b71f6qOPPgWqtVTE2AvolA%3D%3D',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36'}

response = requests.get('https://github.com/', headers=headers_without_cookie)
print('kakawanyifan' in (response.content.decode("UTF-8").lower()))

response = requests.get('https://github.com/', headers=headers_with_cookie)
print('kakawanyifan' in (response.content.decode("UTF-8").lower()))

运行结果:

1
2
False
True

很明显,在没有 cookie 的时候,访问https://github.com/,页面中并不包含kakawanyifan,但是如果带上 cookie 的话,就包含kakawanyifan了。这也说明,有 cookie 和没有 cookie ,拿到的响应并不一样。

利用专门的cookies参数

我们也可以利用专门的cookies参数,该参数的形式是字典。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
import requests

headers_without_cookie = {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36'}

cookies_str = "_octo=GH1.1.920058066.1612922582; tz=Asia%2FShanghai; _device_id=2f0b1ec1e2c816a1f6b6e0cb5ac416a0; has_recent_activity=1; user_session=dSVXhZIHoWFbyksgBhbRS3LPAEmluYDKaOAncCYeJKZeQSod; __Host-user_session_same_site=dSVXhZIHoWFbyksgBhbRS3LPAEmluYDKaOAncCYeJKZeQSod; tz=Asia%2FShanghai; logged_in=yes; dotcom_user=KakaWanYifan; _gh_sess=oKVNid%2Bajb4x2Co7tft%2FMy9YJnHvS7RoPPFSMKK5JJNOU39RyJpwcJO583sEXMvyVYRcENfWnZzyhO%2BI%2Fxfm6QrRLrgyHRIzveXvC8TmmASwyvZk6Sg2tH%2BaFGANDqPN2mKYF8dzN2ZZqonWIVFpxxz9rvpxXlsJ1kjmg6Yc4EkSJYbuD1V9jaBCahxTk7zhXNuzzUincBOfWC%2BUQrHWfOkOSFYFWh7WcdJ%2FNAv2b9f9F5%2FNUzCeWL9hTma2UEY%2F5gNdfCzB1hC23KSB8gylplXWcngEwbeq07LRNd5ZKCCTXxY%2BSJI6kW0PiJPrHQTQdrLHs677XM2B6RmIE7ouqL2D%2FLUEQ92QI5bOdi1FPXtzXl%2BEyjd0VN%2BABhXOFZXQvfC6m58LYdkZC9T9dged88dFhKaCYofDJZ7PNcAq1WSOmMXRMGaaNC5OyqB9dt4cohwVjLa4hGBLChbB33HPNM5Rtiw0cfTIXm1Vdpb%2FQpPo2T9pV2IZiFrdftzq7wgdhMM%2FEITFqqCpJEvjMIPdpeaKHBUnxPfo1flHPzl5vPhSh3Su1diMyMVz73gzewE7ub63bt6CBcsVwBUPEk0VMbWjpYA2hu2oYWoDeSSgzzoNjkwBPmCB6Wn2oY7Nryj5W4L0wiyU0S0QE%2FSBklPpVY4T1jiLZ%2B%2FpumgB4fuuIExCZC1in304cJfgbq0R0gQFEhVeGGLq%2B0q1uUKkkXX1dabEDf5MyedaToXbNF3RjvcyHdMoUzDaP6k93eiBA8XysQEN5EgZVZL4eieWXnhZYlDJ2vH0i0zCS8AMSCweahcHZm80hndGrSlq904ZQ6AiGL2j9r2bZdnO4anhKXy8lBz7sTLwEegamLgbrK21URJC%2F2Whm18j%2FhctuLSG%2Bb%2BctNMuPyL8iyZl0gdVQGrA3o2pBtAuPCml6LTHBQ%3D%3D--bL4%2FbRgWunTs5sxa--QH9XJXbDxrWhWkkvjYQ2Kw%3D%3D"

# 我们需要把那一长串的cookie转换成字典的形式。
cookies_dict = {cookie.split('=')[0]: cookie.split('=')[-1] for cookie in cookies_str.split('; ')}

response = requests.get('https://github.com/', headers=headers_without_cookie, cookies=cookies_dict)
print('kakawanyifan' in (response.content.decode("UTF-8").lower()))

运行结果:

1
True

cookieJar对象转换为cookies字典

截至目前,我们的cookies都是我们打开Chrome浏览器的开发者模式复制过来的。
而且,我们知道

任何东西都有一个日期。沙丁鱼会过期,罐头会过期,就连保鲜纸都会过期。

cookie过期了怎么办?
再打开Chrome浏览器?
在上文我们提到,response有一个cookies属性,该属性值是一个cookieJar类型。
有没有办法把将其转换为cookies字典呢?

转换方法:

1
cookies_dict = requests.utils.dict_from_cookiejar(response.cookies)

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
import requests

headers_without_cookie = {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36'}

cookies_str = "_octo=GH1.1.920058066.1612922582; tz=Asia%2FShanghai; _device_id=2f0b1ec1e2c816a1f6b6e0cb5ac416a0; has_recent_activity=1; user_session=dSVXhZIHoWFbyksgBhbRS3LPAEmluYDKaOAncCYeJKZeQSod; __Host-user_session_same_site=dSVXhZIHoWFbyksgBhbRS3LPAEmluYDKaOAncCYeJKZeQSod; tz=Asia%2FShanghai; logged_in=yes; dotcom_user=KakaWanYifan; _gh_sess=oKVNid%2Bajb4x2Co7tft%2FMy9YJnHvS7RoPPFSMKK5JJNOU39RyJpwcJO583sEXMvyVYRcENfWnZzyhO%2BI%2Fxfm6QrRLrgyHRIzveXvC8TmmASwyvZk6Sg2tH%2BaFGANDqPN2mKYF8dzN2ZZqonWIVFpxxz9rvpxXlsJ1kjmg6Yc4EkSJYbuD1V9jaBCahxTk7zhXNuzzUincBOfWC%2BUQrHWfOkOSFYFWh7WcdJ%2FNAv2b9f9F5%2FNUzCeWL9hTma2UEY%2F5gNdfCzB1hC23KSB8gylplXWcngEwbeq07LRNd5ZKCCTXxY%2BSJI6kW0PiJPrHQTQdrLHs677XM2B6RmIE7ouqL2D%2FLUEQ92QI5bOdi1FPXtzXl%2BEyjd0VN%2BABhXOFZXQvfC6m58LYdkZC9T9dged88dFhKaCYofDJZ7PNcAq1WSOmMXRMGaaNC5OyqB9dt4cohwVjLa4hGBLChbB33HPNM5Rtiw0cfTIXm1Vdpb%2FQpPo2T9pV2IZiFrdftzq7wgdhMM%2FEITFqqCpJEvjMIPdpeaKHBUnxPfo1flHPzl5vPhSh3Su1diMyMVz73gzewE7ub63bt6CBcsVwBUPEk0VMbWjpYA2hu2oYWoDeSSgzzoNjkwBPmCB6Wn2oY7Nryj5W4L0wiyU0S0QE%2FSBklPpVY4T1jiLZ%2B%2FpumgB4fuuIExCZC1in304cJfgbq0R0gQFEhVeGGLq%2B0q1uUKkkXX1dabEDf5MyedaToXbNF3RjvcyHdMoUzDaP6k93eiBA8XysQEN5EgZVZL4eieWXnhZYlDJ2vH0i0zCS8AMSCweahcHZm80hndGrSlq904ZQ6AiGL2j9r2bZdnO4anhKXy8lBz7sTLwEegamLgbrK21URJC%2F2Whm18j%2FhctuLSG%2Bb%2BctNMuPyL8iyZl0gdVQGrA3o2pBtAuPCml6LTHBQ%3D%3D--bL4%2FbRgWunTs5sxa--QH9XJXbDxrWhWkkvjYQ2Kw%3D%3D"
cookies_dict = {cookie.split('=')[0]: cookie.split('=')[-1] for cookie in cookies_str.split('; ')}
print(type(cookies_dict))
response = requests.get('https://github.com/', headers=headers_without_cookie, cookies=cookies_dict)
cookies_dict_get = requests.utils.dict_from_cookiejar(response.cookies)
print(cookies_dict_get)
print(type(cookies_dict_get))

运行结果:

1
2
3
<class 'dict'>
{'__Host-user_session_same_site': 'dSVXhZIHoWFbyksgBhbRS3LPAEmluYDKaOAncCYeJKZeQSod', '_gh_sess': 'EoekAFR2XqxbYq2QoennKctTxkcEqWhJJo4Ef1UqWHpQphu13Hu68tvbr9WxusGtJtqICKbturAXOpZMgZ6ypgddpCzawUHZAX4IIgIgIuLv3fQbwdzPBmtK91YbXDh2BE3G9OkRMx4mG%2F359Alc3ke7w3JxEDRMsZeWUyopzGBJhBjh0owEiiHOpzordEvr0%2Bx0WCctWOh1W2cCjnl6t3NgRv3NxjscvkzpO%2FQB6qXeSKe0fZhTyE%2FqDeHa4oVyLjo8vSfAI6r0uNbU36MBRYu3497wRS0nWbKDzkyevqfb4hwOV%2BRFlXpnF8zgAmpbJA0kcZNbdmWlShxT%2F0R0vu5adlpDnS%2BqqPe9Dne3nMhCLoGkzDdzv8%2FrA2lauY7Ihducnu9nNLv6kVGJ%2B9irEU4vGsmzIen7LSaWusmnNOVF8AbNlVd3b8I%2BiCov%2BxOQuI%2FkNkkdMtnvbvkd%2BVCzcRRUyaTfzj8SowW8qg0pRMBvVAAXr2ZLN9vrubXsbvuOWiPzj%2FOuLrEZFy0gFAFWu6DxdLJs4vDIEO2TBnScfCs5SGsLjt%2FMsY9bA1nSeKyHYznnma4gGmNCz3BlgLCO5KYFG1HsTkQ2xtUSXuh6aZhpBW%2B90H1O%2BTgWuQBQ%2FdE%2B%2FqBc%2BAWYldUOUCMMb9uZz54PtDZRptryI4UHYOhx%2BGf82xvXrbpPnroJaR50IxWDWq79LD6o8gRtfHqd%2FJy7g0vKYRppI3hrj0%2BcLm84biWDU8bco64bW%2BP%2FoyzHsZwH1niZdLQBDxciE9fxyziGIyKjvedQXtOluZL6Wq59XpXLp5Xb%2Fi%2FuR%2BbLtdgr8U5cuG2kbrF%2Bg1vFXz8Q%2FODiiSRz7yX0lcMv1oNk1S0kGXM4Wp7VTuJHwtNyKwFIKczfTmFHkKd08AObAIkLM0xV0x1UOqJnFn7Plaablg%3D%3D--XR1rS8NgCJCS9iGg--65xZ5t5BKxxE3wjIphT02w%3D%3D', 'has_recent_activity': '1', 'user_session': 'dSVXhZIHoWFbyksgBhbRS3LPAEmluYDKaOAncCYeJKZeQSod'}
<class 'dict'>

session的处理

现在,我们来看这么一个现象。
示例代码:

1
2
3
4
5
6
7
import requests

headers_without_cookie = {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36'}

response = requests.get('https://github.com/', headers=headers_without_cookie)
print('Set-Cookie' in response.headers)

运行结果:

1
True

在响应头中有一个Set-Cookie,在上文我们提到了,Set-Cookie是告诉浏览器要添加cookie了。
那么,我们岂不是每次请求,都要检查一下要不要Set Cookie?
不用这么麻烦。
requests中的Session类能够自动处理发送请求获取响应过程中产生的cookie,进而达到状态保持的目的。
我们直接实例化一个session对象

1
session = requests.session()

后续的操作计划没有区别

1
2
response = session.get(url, headers, ...)
response = session.post(url, data, ...)

其他技巧

超时参数timeout的使用

1
response = requests.get(url, timeout=3)
  • timeout的单位是秒

示例代码:

1
2
3
4
import requests

response = requests.get('https://google.com/', timeout=3)
print(response.content.decode())

运行结果:

1
2
3
4
5
6
7
8
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/urllib3/connection.py", line 159, in _new_conn
conn = connection.create_connection(
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/urllib3/util/connection.py", line 84, in create_connection
raise err
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/urllib3/util/connection.py", line 74, in create_connection
sock.connect(sa)
socket.timeout: timed out

参数忽略CA证书

方法

1
response = requests.get(url, verify=False)

示例代码:

1
2
3
4
5
6
7
8
9
10
11
import requests

url = "https://sam.huat.edu.cn:8443/selfservice/"
try:
response = requests.get(url)
print(response.headers)
except Exception as e:
print(e)

response = requests.get(url, verify=False)
print(response.headers)

运行结果:

1
2
3
4
HTTPSConnectionPool(host='sam.huat.edu.cn', port=8443): Max retries exceeded with url: /selfservice/ (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate (_ssl.c:1123)')))
/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/urllib3/connectionpool.py:981: InsecureRequestWarning: Unverified HTTPS request is being made to host 'sam.huat.edu.cn'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
warnings.warn(
{'Server': 'Apache-Coyote/1.1', 'X-Powered-By': 'Servlet 2.5; JBoss-5.0/JBossWeb-2.1', 'P3P': 'CP="CAO PSA OUR"', 'Pragma': 'no-cache, no-cache', 'Cache-Control': 'no-cache, no-cache', 'Expires': 'Thu, 01 Jan 1970 00:00:00 GMT', 'Set-Cookie': 'JSESSIONID=35C99232126734EE6E1E1C1C8300C56C; Path=/selfservice; Secure', 'Content-Type': 'text/html;charset=gbk', 'Content-Length': '5477', 'Date': 'Sun, 21 Feb 2021 07:56:18 GMT'}
文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/11301
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

留言板