发一个HTTP请求-爬虫要做的第一件事

这是python教程的第二篇(第一篇见python和相关软件的安装

一般提到python,就会说用来写爬虫如何如何。

爬虫会自动的发一些http请求,然后解析服务器返回的结果,得到自己想要的数据。

所以教程的第二篇就从几个简单的http请求开始。

先从我们平常上网会接触到的东西开始。

什么是网址

https://www.bilibili.com/

在我们访问一个网站的时候,往往是从一个链接点进去的。比如这里的网址是https://www.bilibili.com。这就是一个URL,或者俗称网址

如果你点进了一个分区,你会发现这个网址变了,比如点进了动画区,网址就会变成https://www.bilibili.com/v/douga/然后这个网址甚至还有可能更复杂,比如https://www.baidu.com/s?wd=123。但是每个网址都对应一个不同的内容。

这个链接就是在网络上,大家用来区分不同的东西所用的统一资源定位系统

  • 统一,是说大家都这么干,B站这么干,微博这么干,百度也这么干。
  • 资源,则是比如网站,图片,或者一个视频,都是网络上的一个资源。
  • 定位,则是用来确定某个资源的位置。

这个网址分为这么几部分

https :// www.baidu.com /s ? wd=123

  • https是说我们访问网站所用到的协议,一般除了https还有http
  • ://用来区分协议和网址的其他部分
  • www.baidu.com域名,比如www.bilibili.com,用来区分不同的网站。

而整个网址剩下的部分,则是用来区分同一个网站不同的内容。

  • /s 则是path,用来在同一个网站中区分不同的内容。
  • ? 用来区分path和路径参数
  • wd=123则是路径参数,用来告诉服务器wd的值是123。比如一个路径参数是username=alice,则是用来告诉服务器用户名是alice。

来用python发一个请求

我们先用命令行安装一个python中常用的客户端

pip install requests

pip是python用的包管理器,用于获取别人写的程序(一个包)。我们这里安装了别人写的requests

然后新建一个py文件,这里叫main.py吧。

import requests

r = requests.get("https://www.bilibili.com/")
print(r.text)

然后在命令行中运行python main.py

会打印出这样的东西

天书

是不是让人头大,看不懂输出的都是什么东西,也看不懂程序干了什么事情。

从http讲起

http请求是简单地一问一答的模式。用户问一句,服务器才答一句。用户发送的是http请求,服务器返回的是http响应

而一个http请求可以简单分为四个部分: 方法 网址 请求头 请求体。 一个http响应则分为状态码头部响应体三部分。

方法除了我们已经用过的GET,还有POSTPATCHPUTDELETE等方法。

但我们常用的只有GETPOST两种,可以按照语意粗略的理解为GET用于从服务器获取数据,POST用于向服务器提交数据,下面也只会用到这两种方法。

我们这里GEThttps://www.bilibili.com/这个网址。

而我们打印出来的r.text则是服务器的响应体

我们用一个http测试网站https://httpbin.org/#/来发一些简单地请求。一步一步的理解一个http请求到底包含什么东西。

import requests

headers = {"hello": "world"}

r = requests.get("https://httpbin.org/headers", headers=headers)
print(r.text)

我们向requests.get传入了headers参数,这是一个python的dict,是python内置的数据类型,表示的是键值对的映射。用来告诉requests中headers的helloworld

这句话可能有些拗口,举个例子:

headers = {"hello": "world"}
print(headers["hello"])

可以看到,程序最后输出了world,表示我们通过这个dict,把"hello"映射到了"world"

我们通过这种方式,用来告诉程序我们这个http请求的headers,而https://httpbin.org/headers会把我们的请求头作为请求体的一部分返回给我们。

所以我们会看到如下的程序运行结果

python main.py
{
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Hello": "world",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.23.0",
"X-Amzn-Trace-Id": "Root=1-5e8010f3-c048dff077b8a11038d41b1c"
}
}

其中"Hello": "world"就是我们传入的headers。其他的结果则是requests默认的请求头。

我们再加几个请求头:

import requests

headers = {
"hello": "world",
"header1": "value1",
"header2": "value2",
"header3": "value3",
"header4": "value4",
}

r = requests.get("https://httpbin.org/headers", headers=headers)
print(r.text)

可以看到结果更多了

python main.py
{
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Header1": "value1",
"Header2": "value2",
"Header3": "value3",
"Header4": "value4",
"Hello": "world",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.23.0",
"X-Amzn-Trace-Id": "Root=1-5e80181d-d51b14b0f5d7e6e095f6e1b0"
}
}

http报文

http请求是基于文本的,我们可以把http报文直接写出来。

请求:

GET /headers
User-Agent: python-requests/2.23.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
hello: world

请求中的第一行是方法和域名右侧的部分,第二行开始是我们的请求头。 (域名在什么地方起作用又是一个很长的故事了)

响应:

HTTP/1.1 200 OK
Date: Sun, 29 Mar 2020 04:09:04 GMT
Content-Type: application/json
Content-Length: 352
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

{
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Hello": "world",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.23.0",
"X-Amzn-Trace-Id": "Root=1-5e801f60-6329e60e07d01478c4013e68"
}
}

前面说过,一个http响应分为状态码头部响应体三部分。

响应的第一行HTTP/1.1 200 OK中,HTTP/1.1是协议,200 OK则是状态码。由一个数字和和一个简单地文本说明来组成。

第二行开始则是响应的头部,跟我们的请求头一样,由一系列的键值对组成,用来提供响应的附加信息。比如Date表示响应的服务器时间,Content-Type表示响应体中数据的格式,Content-Length表示响应体作为文本的长度。

而空行之后到整个响应结束都是响应体,也就是我们打印出来的r.text。是一个纯文本,但是可以以纯文本来表示各种各样的数据。比如这里Content-Typeapplication/json,用到的是json格式,看起来跟python的dict几乎相同。