requests (使用 session、cookie)、httpx、PycURL、you-get、wget、ffmpeg、下载(图片、音乐、视频)

1、requests 的使用

Requests 官方文档:http://cn.python-requests.org/zh_CN/latest

用户指南

从 Requests 的背景讲起,然后对 Requests 的重点功能做了逐一的介绍。

API 文档/指南

如果你要了解具体的函数、类、方法,这部分文档就是为你准备的。

示例:简单使用

安装:pip install requests

import json
import requests

# HTTP 请求类型
r = requests.get('https://github.com/timeline.json')  # get 类型
r = requests.post("http://m.ctrip.com/post")  # post 类型
r = requests.put("http://m.ctrip.com/put")  # put 类型
r = requests.delete("http://m.ctrip.com/delete")  # delete 类型
r = requests.head("http://m.ctrip.com/head")  # head 类型
r = requests.options("http://m.ctrip.com/get")  # options类型

# 获取响应内容
print(r.content)  # 以字节的方式去显示,中文显示为字符
print(r.text)  # 以文本的方式去显示

payload = {'keyword': '日本', 'salecityid': '2'}
# 向 URL 传递参数
r = requests.get("https://m.ctrip.com/webapp/tourvisa/visa_list", params=payload)
print(r.url)

# 获取/修改网页编码
r = requests.get('https://github.com/timeline.json')
print(r.encoding)
# 修改网页编码
r.encoding = 'utf-8'

# json处理
r = requests.get('https://github.com/timeline.json')
print(r.json())  # 需要先 import json

# 定制请求头 (get 和 post 都一样方式)
url = 'https://m.ctrip.com'
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
                  '(KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.62'
}
r = requests.post(url, headers=headers)  # 或者 r = requests.get(url, headers=headers)
print(r.request.headers)


# 复杂post请求
url = 'https://m.ctrip.com'
payload = {'some': 'data'}
# 如果传递的payload是string而不是dict,需要先调用dumps方法格式化一下
r = requests.post(url, data=json.dumps(payload))

# post 多部分编码文件
url = 'https://m.ctrip.com'
files = {'file': open('report.xls', 'rb')}
r = requests.post(url, files=files)


r = requests.get('http://m.ctrip.com')
# 响应状态码
print(r.status_code)

r = requests.get('http://m.ctrip.com')
# 响应头
print(r.headers)

print(r.headers['Content-Type'])
# 访问响应头部分内容的两种方式
print(r.headers.get('content-type'))

# Cookies
url = 'https://example.com/some/cookie/setting/url'
r = requests.get(url)
r.cookies['example_cookie_name']  # 读取 cookies

url = 'https://m.ctrip.com/cookies'
cookies = dict(cookies_are='working')
r = requests.get(url, cookies=cookies)  # 发送cookies

# 设置超时时间
r = requests.get('https://m.ctrip.com', timeout=0.001)

# 设置访问代理(get 和 post 都一样方式)
proxies = {
    "http": "http://10.10.10.10:8888",
    "https": "http://10.10.10.100:4444",
}
r = requests.get('https://m.ctrip.com', proxies=proxies)

发送 get 请求、传递参数

import requests

r = requests.get("http://httpbin.org/get")
print(type(r))
print(r.status_code)  # 获取响应状态码
print(r.encoding)  # 获取网页编码
print(r.text)  # r.text来获取响应的内容。以字符的形式获取
print(r.content)  # 以字节的形式获取响应的内容。requests会自动将内容转码。
print(r.cookies)  # 获取cookies

requests.get('https://github.com/timeline.json')   # GET请求
requests.post('https://httpbin.org/post')           # POST请求
requests.put('https://httpbin.org/put')             # PUT请求
requests.delete('https://httpbin.org/delete')       # DELETE请求
requests.head('https://httpbin.org/get')            # HEAD请求
requests.options('https://httpbin.org/get')         # OPTIONS请求

requests 模块

  • 发送 get 请求时,请求参数可以直接放在 ur1 的?后面,也可以放在字典里,传递给params
  • 发送 post 请求时,请求参数要放在字典里,传递给 data
import requests

payload = {'key1': 'value1', 'key2': 'value2'}
r = requests.get("http://httpbin.org/get", params=payload)
print r.url

运行结果

http://httpbin.org/get?key2=value2&key1=value1
url = "https://www.baidu.com/s"
r = requests.get(url, params={'wd': 'python'})
print r.url
r = requests.get(url, params={'wd': 'php'})
print r.url
print r.text

示例:带参数的请求

import requests

# GET参数实例
requests.get('https://www.baidu.com/s', params={'wd': 'python'})
# POST参数实例
requests.post(
    'https://www.itwhy.org/wp-comments-post.php',
    data={'comment': '测试POST'}
)

# get 方法 使用 params 参数传递数据
# post 方法 使用 data 参数传递数据

r = requests.get("https://httpbin.org/get", params={'wd': 'python'})  # get 参数示例
print(r.url)
print(r.text)

# post 方法 如果使用 params 参数传递数据时,传递的数据可以在url中以明文看到
r = requests.post("https://httpbin.org/post", params={'wd': 'python'})
print(r.url)
print(r.text)

# post 如果使用 data 参数传递数据时,传递的数据在url中无法看到
r = requests.post(
    "https://httpbin.org/post", 
    data={'comment': 'TEST_POST'}
)  # post 参数示例
print(r.url)
print(r.text)

如果想请求JSON文件,可以利用 json() 方法解析。例如自己写一个JSON文件命名为a.json,内容如下

["foo", "bar", {
  "foo": "bar"
}]

利用如下程序请求并解析

import requests

# a.json 代表的一个服务器json文件,这里为了演示
# 实际是:http://xxx.com/a.json  形式的URL地址
r = requests.get("a.json") 
print(r.text)
print(r.json())

运行结果如下,其中一个是直接输出内容,另外一个方法是利用 json() 方法解析。

["foo", "bar", {
 "foo": "bar"
 }]
 [u'foo', u'bar', {u'foo': u'bar'}]

如果想获取来自服务器的原始套接字响应,可以取得 r.raw 。 不过需要在初始请求中设置 stream=True 。

r = requests.get('https://github.com/timeline.json', stream=True)
r.raw
<requests.packages.urllib3.response.HTTPResponse object at 0x101194810>
r.raw.read(10)
'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'

这样就获取了网页原始套接字内容。如果想添加 headers,可以传 headers 参数

import requests

payload = {'key1': 'value1', 'key2': 'value2'}
headers = {'content-type': 'application/json'}
r = requests.get("http://httpbin.org/get", params=payload, headers=headers)
print(r.url)

通过headers参数可以增加请求头中的headers信息

自定义 header

import requests
import json
 
data = {'some': 'data'}
headers = {
    'content-type': 'application/json',
    'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:22.0)'
}
 
r = requests.post('https://api.github.com/some/endpoint', data=data, headers=headers)
print(r.text)

请求头内容可以用r.request.headers来获取。

>>> r.request.headers

自定义请求头部:定制 headers,使用 headers 参数来传递

伪装请求头部是采集时经常用的,我们可以用这个方法来隐藏:

r = requests.get('http://www.zhidaow.com')
print(r.request.headers['User-Agent'])

headers = {'User-Agent': 'alexkh'}
r = requests.get('http://www.zhidaow.com', headers = headers)
print(r.request.headers['User-Agent'])

POST 请求

请求参数,观察抓包的参数状况: 

  • QueryStringParameters   --->   url
  • Form Data   --->   requests.post(data)
  • requestpayload   --->  requests.post(data=json.dumps(dict),headers={"contentType":"application/json"})
"""
requests  -> 

    get:
        Query String Parameters  ->  url
        url上拼接? xxx=xxx&xxxx=xxxx
        params -> 也可以把上述参数进行设置
    
    post:
        Form Data
            把字典传递个data即可
            requests.post(url, data=dict)
        Request Payload
            把字典处理成json传递给data
            字典处理成json之后. json是不是字符串????
            同时需要给出请求头中的Content-Type : application/json
"""

对于 POST 请求一般需要为它增加一些参数。最基本的传参方法可以利用 data 这个参数。

import requests

payload = {'key1': 'value1', 'key2': 'value2'}
r = requests.post("http://httpbin.org/post", data=payload)
print(r.text)

执行成功后,服务器返回了我们传的数据。如果需要传JSON格式的数据,可以用 json.dumps() 方法把表单数据序列化。

POST发送JSON数据:

import json
import requests

url = 'http://httpbin.org/post'
payload = {'some': 'data'}
r = requests.post(url, data=json.dumps(payload))
print(r.text)

data 参数

  • 当传递给requests.post方法的data参数是字典类型时,requests会自动将其编码为表单形式(application/x-www-form-urlencoded),这是大多数HTTP表单提交的默认编码方式。
  • 如果data参数是字符串,则可以自定义数据格式,如JSON字符串。此时,你可能需要手动设置headers参数中的'Content-Type''application/json',或其他适合你数据格式的MIME类型。
  • data参数可以是字典、字节序列,或文件对象的元组列表。

json 参数

  • json参数提供了一种更直接的方式来发送JSON编码的数据。当使用json参数时,requests会自动将字典编码为JSON格式,并自动将Content-Type设置为application/json
  • 使用json参数时,无需调用json.dumps()来序列化数据,requests会处理这一过程。

结论

  • 使用data参数更为通用,适用于提交表单数据或发送自定义格式的数据。但要正确发送JSON数据,需要手动设置Content-Type头部。
  • 使用json参数时,适用于发送JSON数据,更为便捷,不需要手动指定Content-Type头部,因为requests会自动处理。

post 请求载体是表单时,则 data参数 是 字典

post 请求载体是载荷时,data参数是字符串,不是字典

import requests
import json

headers = {
    "Content-Type": "application/json",
    "u-sign": "f4f3ddab7f10a7d927fdfef3a7a3ca2d",
}
url = "https://uwf7de983aad7a717eb.youzy.cn/youzy.dms.datalib.api.enrolldata.enter.college.encrypted.v2.get"
data_dict = {
    "collegeCode": "10001",
    "provinceCode": 37
}
data_string = json.dumps(data_dict, separators=(',', ':'))
response = requests.post(url, headers=headers, data=data_string)

print(response.text)
print(response)

一般而言,如果是提交JSON数据给服务器,使用json参数会更加方便。如果提交其他格式的数据或表单数据,使用data参数会更适合。

如果想要上传文件,那么直接用 file 参数即可。新建一个 a.txt 的文件,内容写上 Hello World!

import requests

url = 'http://httpbin.org/post'
files = {'file': open('test.txt', 'rb')}
r = requests.post(url, files=files)
print r.text

运行结果如下

{
  "args": {}, 
  "data": "", 
  "files": {
    "file": "Hello World!"
  }, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Content-Length": "156", 
    "Content-Type": "multipart/form-data; boundary=7d8eb5ff99a04c11bb3e862ce78d7000", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.9.1"
  }, 
  "json": null, 
  "url": "http://httpbin.org/post"
}

这样便成功完成了一个文件的上传。
requests 是支持流式上传的,这允许你发送大的数据流或文件而无需先把它们读入内存。要使用流式上传,仅需为你的请求体提供一个类文件对象即可。这是一个非常实用方便的功能。

with open('massive-body') as f:
    requests.post('http://some.url/streamed', data=f)
import requests
 
url = 'http://127.0.0.1:5000/upload'
files = {'file': open('/home/lyb/sjzl.mpg', 'rb')}
#files = {'file': ('report.jpg', open('/home/lyb/sjzl.mpg', 'rb'))}     #显式的设置文件名
 
r = requests.post(url, files=files)
print(r.text)

你可以把字符串当着文件进行上传:

import requests
 
url = 'http://127.0.0.1:5000/upload'
files = {'file': ('test.txt', b'Hello Requests.')}     #必需显式的设置文件名
 
r = requests.post(url, files=files)
print(r.text)

发送文件的post类型,这个相当于向网站上传一张图片,文档等操作,这时要使用files参数

>>> url = 'http://httpbin.org/post'
>>> files = {'file': open('touxiang.png', 'rb')}
>>> r = requests.post(url, files=files)

POST 请求模拟登陆及一些返回对象的方法

import requests

url1 = 'https://www.exanple.com/login'  # 登陆地址
url2 = "https://www.example.com/main"  # 需要登陆才能访问的地址
data = {"user": "user", "password": "pass"}
headers = {
    "Accept": "text/html,application/xhtml+xml,application/xml;",
    "Accept-Encoding": "gzip",
    "Accept-Language": "zh-CN,zh;q=0.8",
    "Referer": "https://www.example.com/",
    "User-Agent": ""
}
res1 = requests.post(url1, data=data, headers=headers)
res2 = requests.get(url2, cookies=res1.cookies, headers=headers)
print(res2.content)  # 获得二进制响应内容
print(res2.raw)  # 获得原始响应内容,需要stream=True
print(res2.raw.read(50))
print(type(res2.text))  # 返回解码成unicode的内容
print(res2.url)
print(res2.history)  # 追踪重定向
print(res2.cookies)
print(res2.cookies['example_cookie_name'])
print(res2.headers)
print(res2.headers['Content-Type'])
print(res2.headers.get('content-type'))
print(res2.json)  # 讲返回内容编码为json
print(res2.encoding)  # 返回内容编码
print(res2.status_code)  # 返回http状态码
print(res2.raise_for_status())  # 返回错误状态码

Response 对象

使用requests方法后,会返回一个response对象,其存储了服务器响应的内容,如上实例中已经提到的 r.text、r.status_code……
获取文本方式的响应体实例:当你访问 r.text 之时,会使用其响应的文本编码进行解码,并且你可以修改其编码让 r.text 使用自定义的编码进行解码。

r.status_code    # 响应状态码
r.raw            # 返回原始响应体,也就是 urllib 的 response 对象,使用 r.raw.read() 读取
r.content        # 字节方式的响应体,会自动为你解码 gzip 和 deflate 压缩
r.text           # 字符串方式的响应体,会自动根据响应头部的字符编码进行解码
r.headers        # 以字典对象存储服务器响应头,但是这个字典比较特殊,字典键不区分大小写,若键不存在则返回None

#*特殊方法*#
r.json()                # Requests中内置的JSON解码器
r.raise_for_status()    # 失败请求(非200响应)抛出异常

Cookies

如果一个响应中包含了cookie,那么我们可以利用 cookies 变量来拿到。会话对象让你能够跨请求保持某些参数,最方便的是在同一个Session实例发出的所有请求之间保持cookies,且这些都是自动处理的

import requests

url = 'http://example.com'
r = requests.get(url)
print r.cookies
print r.cookies['example_cookie_name']

以上程序仅是样例,可以用 cookies 变量来得到站点的 cookies。另外可以利用 cookies 变量来向服务器发送 cookies 信息

import requests

url = 'http://httpbin.org/cookies'
cookies = dict(cookies_are='working')
r = requests.get(url, cookies=cookies)
print r.text

运行结果
'{"cookies": {"cookies_are": "working"}}'

如果某个响应中包含一些Cookie,你可以快速访问它们:

import requests

r = requests.get('http://www.google.com.hk/')
print(r.cookies['NID'])
print(tuple(r.cookies))

要想发送你的cookies到服务器,可以使用 cookies 参数:

import requests
 
url = 'http://httpbin.org/cookies'
cookies = {'testCookies_1': 'Hello_Python3', 'testCookies_2': 'Hello_Requests'}
# 在Cookie Version 0中规定空格、方括号、圆括号、等于号、逗号、双引号、斜杠、问号、@,冒号,分号等特殊符号都不能作为Cookie的内容。
r = requests.get(url, cookies=cookies)
print(r.json())

如下是快盘签到脚本

import requests
 
headers = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
           'Accept-Encoding': 'gzip, deflate, compress',
           'Accept-Language': 'en-us;q=0.5,en;q=0.3',
           'Cache-Control': 'max-age=0',
           'Connection': 'keep-alive',
           'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:22.0) Gecko/20100101 Firefox/22.0'}
 
s = requests.Session()
s.headers.update(headers)
# s.auth = ('superuser', '123')
s.get('https://www.kuaipan.cn/account_login.htm')
 
_URL = 'http://www.kuaipan.cn/index.php'
s.post(_URL, params={'ac':'account', 'op':'login'},
       data={'username':'****@foxmail.com', 'userpwd':'********', 'isajax':'yes'})
r = s.get(_URL, params={'ac':'zone', 'op':'taskdetail'})
print(r.json())
s.get(_URL, params={'ac':'common', 'op':'usersign'})

获取响应中的cookies

获取响应中的cookies
>>> r = requests.get('http://www.baidu.com')
>>> r.cookies['BAIDUID']
'D5810267346AEFB0F25CB0D6D0E043E6:FG=1'

也可以自已定义请求的COOKIES
>>> url = 'http://httpbin.org/cookies'
>>> cookies = {'cookies_are':'working'}
>>> r = requests.get(url,cookies = cookies)
>>> 
>>> print r.text
{
  "cookies": {
    "cookies_are": "working"
  }
}
>>>

超时 配置

可以利用 timeout 变量来配置最大请求时间。timeout 仅对连接过程有效,与响应体的下载无关。

requests.get('http://github.com', timeout=0.001)

注:timeout 仅对连接过程有效,与响应体的下载无关。

也就是说,这个时间只限制请求的时间。即使返回的 response 包含很大内容,下载需要一定时间,然而这并没有什么卵用。

Session

requests 爬虫:http://www.cnblogs.com/lucky-pin/p/5806394.html

在了解 Session 和 Cookie 之前,需要先了解 HTTP 的一个特点,叫作无状态

无状态 HTTP

HTTP 的无状态是指 HTTP 协议对事务处理是没有记忆能力的,或者说服务器并不知道客户端处于什么状态。客户端向服务器发送请求后,服务器解析此请求,然后返回对应的响应,服务器负责完成这个过程,而且这个过程是完全独立的,服务器不会记录前后状态的变化,也就是缺少状态记录。这意味着之后如果需要处理前面的信息,客户端就必须重传,导致需要额外传递一些重复请求,才能获取后续响应,这种效果显然不是我们想要的。为了保持前后状态,肯定不能让客户端将前面的请求全部重传一次,这太浪费资源了,对于需要用户登录的页面来说,更是棘手
这时两种用于保持HTTP连接状态的技术出现了,分别是Session和Cookie。
Session 在服务端也就是网站的服务器,用来保存用户的 Session 信息;
Cookie 在客户端,也可以理解为在浏览器端。有了 Cookie,浏览器在下次访问相同网页时就会自动附带上它,并发送给服务器,服务器通过识别Cookie 鉴定出是哪个用户在访问,然后判断此用户是否处于登录状态,并返回对应的响应。
可以这样理解,Cookie里保存着登录的凭证,客户端在下次请求时只需要将其携带上,就不必重新输人用户名、密码等信息重新登录了
因此在爬虫中,处理需要先登录才能访间的页面时,我们一般会直接将登录成功后获取的 Cookie放在请求头里面直接请求,而不重新模拟登录。

什么是 Session

Session 中文称之为会话,其本义是指有始有终的一系列动作、消息。例如打电话时,从拿起电话拨号到挂断电话之间的一系列过程就可以称为一个Session。
而在Web中Session对象用来存储特定用户Session 所需的属性及配置信息。这样,当用户在应用程序的页面之间跳转时,存储在Session 对象中的变量将不会丢失,会在整个用户Session中一直存在下去。当用户请求来自应用程序的页面时,如果该用户还没有 Session,那么 Web 服务器将自动创建一个Session对象。当Session过期或被放弃后,服务器将终止该Session。

什么是 Cookie

Cookie 指某些网站为了鉴别用户身份、进行Session跟踪而存储在用户本地终端上的数据

Session 维持原理

那么,怎样利用 Cookie 保持状态呢?在客户端第一次请求服务器时,服务器会返回一个响应头中带有 Set-Cookie字段的响应给客户端,这个字段用来标记用户。客户端浏览器会把 Cookie 保存起来,当下一次请求相同的网站时,把保存的 Cookie 放到请求头中一起提交给服务器。Cookie 中携带着SessionID相关信息,服务器通过检查Cookie 即可找到对应的Session,继而通过判断Session辨认用户状态。如果 Session 当前是有效的,就证明用户处于登录状态,此时服务器返回登录之后才可以查看的网页内容,浏览器再进行解析便可以看到了。
反之,如果传给服务器的Cookie是无效的,或者 Session已经过期了,客户端将不能继续访问页面,此时可能会收到错误的响应或者跳转到登录页面重新登录
Cookie和Session需要配合,一个在客户端,一个在服务端,二者共同协作,就实现了登录控制

常见误区

在谈论 Session 机制的时候,常会听到一种误解-只要关闭浏览器,Session 就消失了。可以想象一下生活中的会员卡,除非顾客主动对店家提出销卡,否则店家是绝对不会轻易删除顾客资料的。对Session来说,也一样,除非程序通知服务器删除一个Session,否则服务器会一直保留。例如:程序一般都是在我们做注销操作时才删除Session。
但是当我们关闭浏览器时,浏览器不会主动在关闭之前通知服务器自己将要被关闭,所以服务器压根不会有机会知道浏览器已经关闭。之所以会产生上面的误解,是因为大部分网站使用会话 Cookie来保存SessionID信息,而浏览器关闭后Cookie就消失了,等浏览器再次连接服务器时,也就无法找到原来的 Session了。如果把服务器设置的 Cookie 保存到硬盘上,或者使用某种手段改写浏览器发出的 HTTP 请求头,把原来的 Cookie 发送给服务器,那么再次打开浏览器时,仍然能够找到原来的SessionID,依旧保持登录状态
而且恰恰是由于关闭浏览器不会导致Session 被删除,因此需要服务器为Session设置一个失效时间,当距离客户端上一次使用Session 的时间超过这个失效时间时,服务器才可以认为客户端已经停止了活动,并删除掉 Session 以节省存储空间。

requests 中使用 session

使用 session 步骤
    1. 先初始化一个session对象,s = requests.Session()
    2. 然后使用这个session对象来进行访问,r = s.post(url,data = user)

在以上的请求中,每次请求其实都相当于发起了一个新的请求。也就是相当于我们每个请求都用了不同的浏览器单独打开的效果。也就是它并不是指的一个会话,即使请求的是同一个网址。

比如

import requests

requests.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
r = requests.get("http://httpbin.org/cookies")
print(r.text)
结果是:
{
  "cookies": {}
}

很明显,这不在一个会话中,无法获取 cookies,那么在一些站点中,我们需要保持一个持久的会话怎么办呢?就像用一个浏览器逛淘宝一样,在不同的选项卡之间跳转,这样其实就是建立了一个长久会话。

解决方案如下

import requests

s = requests.Session()
s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
r = s.get("http://httpbin.org/cookies")
print(r.text)

在这里我们请求了两次,一次是设置 cookies,一次是获得 cookies。运行结果

{
  "cookies": {
    "sessioncookie": "123456789"
  }
}

发现可以成功获取到 cookies 了,这就是建立一个会话到作用。那么既然会话是一个全局的变量,那么我们肯定可以用来全局的配置了。

import requests

s = requests.Session()
s.headers.update({'x-test': 'true'})
r = s.get('http://httpbin.org/headers', headers={'x-test2': 'true'})
print r.text

通过 s.headers.update 方法设置了 headers 的变量。然后我们又在请求中设置了一个 headers,那么会出现什么结果?

很简单,两个变量都传送过去了。运行结果:

{
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.9.1", 
    "X-Test": "true", 
    "X-Test2": "true"
  }
}

如果get方法传的headers 同样也是 x-test 呢?

r = s.get('http://httpbin.org/headers', headers={'x-test': 'true'})

嗯,它会覆盖掉全局的配置

{
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.9.1", 
    "X-Test": "true"
  }
}

那如果不想要全局配置中的一个变量了呢?很简单,设置为 None 即可

r = s.get('http://httpbin.org/headers', headers={'x-test': None})

运行结果

{
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.9.1"
  }
}

以上就是 session 会话的基本用法

使用Session()对象的写法(Prepared Requests):

#-*- coding:utf-8 -*-
import requests
s = requests.Session()
url1 = 'http://www.exanple.com/login'#登陆地址
url2 = "http://www.example.com/main"#需要登陆才能访问的地址
data={"user":"user","password":"pass"}
headers = { "Accept":"text/html,application/xhtml+xml,application/xml;",
            "Accept-Encoding":"gzip",
            "Accept-Language":"zh-CN,zh;q=0.8",
            "Referer":"http://www.example.com/",
            "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36"
            }

prepped1 = requests.Request('POST', url1,
    data=data,
    headers=headers
).prepare()
s.send(prepped1)


'''
也可以这样写
res = requests.Request('POST', url1,
data=data,
headers=headers
)
prepared = s.prepare_request(res)
# do something with prepped.body
# do something with prepped.headers
s.send(prepared)
'''

prepare2 = requests.Request('POST', url2,
    headers=headers
).prepare()
res2 = s.send(prepare2)

print res2.content

另一种写法 :

#-*- coding:utf-8 -*-
import requests
s = requests.Session()
url1 = 'http://www.exanple.com/login'#登陆地址
url2 = "http://www.example.com/main"#需要登陆才能访问的页面地址
data={"user":"user","password":"pass"}
headers = { "Accept":"text/html,application/xhtml+xml,application/xml;",
            "Accept-Encoding":"gzip",
            "Accept-Language":"zh-CN,zh;q=0.8",
            "Referer":"http://www.example.com/",
            "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36"
            }
res1 = s.post(url1, data=data)
res2 = s.post(url2)
print(resp2.content)

SSL证书验证

现在随处可见 https 开头的网站,Requests可以为HTTPS请求验证SSL证书,就像web浏览器一样。要想检查某个主机的SSL证书,你可以使用 verify 参数。
现在 12306 证书不是无效的嘛,来测试一下

import requests

r = requests.get('https://kyfw.12306.cn/otn/', verify=True)
print r.text
结果
requests.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:590)
果真如此

来试下 github 的

import requests

r = requests.get('https://github.com', verify=True)
print r.text

正常请求,内容我就不输出了。如果我们想跳过刚才 12306 的证书验证,把 verify 设置为 False 即可

import requests

r = requests.get('https://kyfw.12306.cn/otn/', verify=False)
print r.text

发现就可以正常请求了。在默认情况下 verify 是 True,所以如果需要的话,需要手动设置下这个变量。

身份验证

基本身份认证(HTTP Basic Auth):

import requests
from requests.auth import HTTPBasicAuth
 
r = requests.get('https://httpbin.org/hidden-basic-auth/user/passwd', auth=HTTPBasicAuth('user', 'passwd'))
# r = requests.get('https://httpbin.org/hidden-basic-auth/user/passwd', auth=('user', 'passwd'))    # 简写
print(r.json())

另一种非常流行的HTTP身份认证形式是摘要式身份认证,Requests对它的支持也是开箱即可用的:

requests.get(URL, auth=HTTPDigestAuth('user', 'pass'))

requests 设置 http、socks 代理

使用代理可以通过为任意请求方法提供 proxies 参数来配置单个请求

import requests

proxies = {
  "https": "http://41.118.132.69:4433"
}
r = requests.post("http://httpbin.org/post", proxies=proxies)
print r.text

也可以通过环境变量 HTTP_PROXY 和 HTTPS_PROXY 来配置代理

export HTTP_PROXY="http://10.10.1.10:3128"
export HTTPS_PROXY="http://10.10.1.10:1080"

采集时为避免被封IP,经常会使用代理。requests也有相应的proxies属性。

import requests

proxies = {
"http": "http://10.10.1.10:3128",
"https": "http://10.10.1.10:1080",
}

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

如果代理需要账户和密码,则需这样:

proxies = {
"http": "http://user:pass@10.10.1.10:3128/",
}

示例:

import requests
 
proxy = '127.0.0.1:10809'
proxies = {
    'http': 'http://' + proxy,
    'https': 'https://' + proxy,
}
try:
    response = requests.get('http://httpbin.org/get', proxies=proxies)
    print(response.text)
except requests.exceptions.ConnectionError as e:
    print('Error', e.args)

如果代理需要认证,同样在代理的前面加上用户名密码即可,代理的写法就变成:

proxy = 'username:password@127.0.0.1:10809'

如果需要使用 SOCKS5 代理,则首先需要安装一个 Socks 模块:

pip3 install "requests[socks]"

同样使用本机运行代理软件的方式,则爬虫设置代理的代码如下:

import requests
 
proxy = '127.0.0.1:10808'
proxies = {
    'http': 'socks5://' + proxy,
    'https': 'socks5://' + proxy
}
try:
    response = requests.get('http://httpbin.org/get', proxies=proxies)
    print(response.text)
except requests.exceptions.ConnectionError as e:
    print('Error', e.args)

还有一种使用 socks 模块进行全局设置的方法,如下:

import requests
import socks
import socket
 
socks.set_default_proxy(socks.SOCKS5, '127.0.0.1', 10808)
socket.socket = socks.socksocket
try:
    response = requests.get('http://httpbin.org/get')
    print(response.text)
except requests.exceptions.ConnectionError as e:
    print('Error', e.args)

如果代理提供了协议,就使用对应协议的代理;如果代理没有协议的话,就在代理上加上http协议。

对于 Chrome 来说,用 Selenium 设置代理的方法也非常简单,设置方法如下:

from selenium import webdriver
 
proxy = '127.0.0.1:10809'
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--proxy-server=http://' + proxy)
browser = webdriver.Chrome(chrome_options=chrome_options)
browser.get('http://httpbin.org/get')

socks5 代理

下图是 ccxt 的代理 (纯娱乐)

或者 (可尝试把这代码加到最前面,改成对应的端口)

import socks
import socket

# # set proxy
socks.set_default_proxy(socks.SOCKS5, "127.0.0.1", 10808)
socket.socket = socks.socksocket

利用 Requests 来抓取火车票数据。

根据观察,数据接口如下:

https://kyfw.12306.cn/otn/lcxxcx/query?purpose_codes=ADULT&queryDate=2015-05-23&from_station=NCG&to_station=CZQ

返回的是2015-5-23南昌到郴州的火车票信息,格式为json。
返回的数据的如下(只截取了一部分):

{"validateMessagesShowId":"_validatorMessage","status":true,"httpstatus":200,"data":{"datas":[{"train_no":"5u000G140101","station_train_code":"G1401","start_station_telecode":"NXG","start_station_name":"南昌西","end_station_telecode":"IZQ","end_station_name":"广州南","from_station_telecode":"NXG","from_station_name":"南昌西","to_station_telecode":"ICQ","to_station_name":"郴州西","start_time":"07:29","arrive_time":"10:42","day_difference":"0","train_class_name":"","lishi":"03:13","canWebBuy":"Y","lishiValue":"193","yp_info":"O030850182M0507000009097450000","control_train_day":"20991231","start_train_date":"20150523","seat_feature":"O3M393","yp_ex":"O0M090","train_seat_feature":"3","seat_types":"OM9","location_code":"G2","from_station_no":"01","to_station_no":"11","control_day":59,"sale_time":"0930","is_support_card":"1","note":"","gg_num":"--","gr_num":"--","qt_num":"--","rw_num":"--","rz_num":"--","tz_num":"--","wz_num":"--","yb_num":"--","yw_num":"--","yz_num":"--","ze_num":"182","zy_num":"无","swz_num":"无"}}

看着很乱,我们稍加整理:

{
    "validateMessagesShowId":"_validatorMessage",
    "status":true,"httpstatus":200,
    "data":{
            "datas":[
                        {
                             "train_no":"5u000G140101",
                             "station_train_code":"G1401",
                             "start_station_telecode":"NXG",
                             "start_station_name":"南昌西",
                             "end_station_telecode":"IZQ",
                             "end_station_name":"广州南",
                             "from_station_telecode":"NXG",
                             "from_station_name":"南昌西",
                             "to_station_telecode":"ICQ",
                             "to_station_name":"郴州西",
                             "start_time":"07:29",
                             "arrive_time":"10:42",
                             "day_difference":"0",
                             ...
                             "swz_num":"无"
                        },
                        {
                              ...
                        }
                    ]
}

这样就比较清晰了,代码如下,提取自己需要的信息。

#-*- coding:utf-8 -*-
import requests
import json

class trainTicketsSprider:

    def getTicketsInfo(self,purpose_codes,queryDate,from_station,to_station):
        self.url = 'https://kyfw.12306.cn/otn/lcxxcx/query?purpose_codes=%s&queryDate=%s&from_station=%s&to_station=%s' %(purpose_codes,queryDate,from_station,to_station)
        self.headers = { 
                    "Accept":"text/html,application/xhtml+xml,application/xml;",
                    "Accept-Encoding":"gzip",
                    "Accept-Language":"zh-CN,zh;q=0.8",
                    "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36"
                  }
        self.TicketSession = requests.Session()
        self.TicketSession.verify = False #关闭https验证   
        self.TicketSession.headers = self.headers
        try:
            self.resp_json = self.TicketSession.get(self.url)
            self.ticketsDatas = json.loads(self.resp_json.text)["data"]["datas"]
            return self.ticketsDatas
        except Exception,e:
            print e

def isZero(num):
    if num == '--' or '无':
        return '0'
    else:
        return num

def main():
    purpose_codes = 'ADULT'
    queryDate = '2015-05-23'
    from_station = 'NCG'
    to_station = 'CZQ'
    TicketSprider = trainTicketsSprider()
    res= TicketSprider.getTicketsInfo(purpose_codes,queryDate,from_station,to_station)
    for i,ticketInfo in enumerate(res):        
                print u"车次:%s" %ticketInfo["station_train_code"]
                print u"起始站:%s" %ticketInfo["start_station_name"]
                print u"目的地:%s" %ticketInfo["to_station_name"]
                print u"开车时间:%s" %ticketInfo["start_time"]
                print u"到达时间:%s" %ticketInfo["arrive_time"]
                print u"二等座还剩:%s张票" %isZero(ticketInfo["ze_num"])
                print u"硬座还剩:%s张票" %isZero(ticketInfo["yz_num"])
                print u"硬卧还剩:%s张票" %isZero(ticketInfo["yw_num"])
                print u"无座还剩:%s张票" %isZero(ticketInfo["wz_num"])
                print u"是否有票:%s" %ticketInfo["canWebBuy"]
                print "**********************************"


if __name__ == '__main__':
    main()

Requests POST 多部分编码 (Multipart-Encoded) 的文件方法

http://lovesoo.org/requests-post-multiple-part-encoding-multipart-encoded-file-format.html

更多请参考:

1. 快速上手 — Requests 2.18.1 文档

2. Uploading Data — requests_toolbelt 0.10.1 documentation

Requests本身虽然提供了简单的方法POST多部分编码(Multipart-Encoded)的文件,但是Requests是先读取文件到内存中,然后再构造请求发送出去。
如果需要发送一个非常大的文件作为 multipart/form-data 请求时,为了避免把大文件读取到内存中,我们就希望将请求做成数据流。
默认requests是不支持的(或很困难), 这时需要用到第三方包requests-toolbelt。
两个库POST多部分编码(Multipart-Encoded)的文件示例代码分别如下:

Requests库(先读取文件至内存中)

import requests
 
url = 'http://httpbin.org/post'
files = {'file': open('report.xls', 'rb')}
 
r = requests.post(url, files=files)
print r.text

Requests+requests-toolbelt库(直接发送数据流)

 
# -*- coding:utf-8 -*-

import requests
from requests_toolbelt.multipart.encoder import MultipartEncoder

proxies = {
    "http": "http://172.17.18.80:8080",
    "https": "http://172.17.18.80:8080",
}

if __name__ == "__main__":
    print "main"

    m = MultipartEncoder(
        fields={'field0': 'value', 'field1': 'value',
                'field2': ('names.txt', open(r'd:\names.txt', 'r'), 'application/zip')}
    )

    r = requests.post('http://httpbin.org/post',
                      data=m,
                      headers={'Content-Type': m.content_type},
                      proxies=proxies)
    print r.text

模拟登录豆瓣、发表动态

github 上一个关于模拟登录的项目https://github.com/xchaoinfo/fuck-login

模拟登陆的重点,在于找到表单真实的提交地址,然后携带 cookie,然后 post 数据即可,只要登陆成功,就可以访问其他任意网页,从而获取网页内容。

一个请求,只要正确模拟了method,url,header,body 这四要素,任何内容都能抓下来,而所有的四个要素,只要打开浏览器-审查元素-Network就能看到!

验证码这一块,现在主要是先把验证码的图片保存下来,手动输入验证码,后期研究下python自动识别验证码。如果验证码保存成本地图片,看的不不太清楚(有时间在改下),可以把验证码的 url 地址在浏览器中打开,就可以看清楚验证码了。

主要实现 登录豆瓣,并发表一句话

# -*- coding:utf-8 -*-

import re
import requests
from bs4 import BeautifulSoup


class DouBan(object):
    def __init__(self):
        self.__username = "豆瓣帐号" # 豆瓣帐号
        self.__password = "豆瓣密码" # 豆瓣密码
        self.__main_url = "https://www.douban.com"
        self.__login_url = "https://www.douban.com/accounts/login"
        self.__proxies = {
            "http": "http://172.17.18.80:8080",
            "https": "https://172.17.18.80:8080"
        }
        self.__headers = {
            "Host": "www.douban.com",
            "Origin": self.__main_url,
            "Referer": self.__main_url,
            "Upgrade-Insecure-Requests": "1",
            "User-Agent": 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
        }
        self.__data = {
            "source": "index_nav",
            "redir": "https://www.douban.com",
            "form_email": self.__username,
            "form_password": self.__password,
            "login": u"登录"
        }

        self.__session = requests.session()
        self.__session.headers = self.__headers
        self.__session.proxies = self.__proxies
        pass

    def login(self):
        r = self.__session.post(self.__login_url, self.__data)
        if r.status_code == 200:
            html = r.content
            soup = BeautifulSoup(html, "lxml")
            captcha_address = soup.find('img', id='captcha_image')['src']
            print captcha_address
            # 验证码存在
            if captcha_address:
                # 利用正则表达式获取captcha的ID
                re_captcha_id = r'<input type="hidden" name="captcha-id" value="(.*?)"/'
                captcha_id = re.findall(re_captcha_id, html)
                print captcha_id
                # 保存到本地
                with open('captcha.jpg', 'w') as f:
                    f.write(requests.get(captcha_address, proxies=self.__proxies).content)
                captcha = raw_input('please input the captcha:')

                self.__data['captcha-solution'] = captcha
                self.__data['captcha-id'] = captcha_id
                r = self.__session.post(self.__login_url, data=self.__data)
                if r.status_code == 200:
                    print "login success"
                    data = {
                        "ck": "NBJ2",
                        "comment": "模拟登录"
                    }
                    r = self.__session.post(self.__main_url, data=data)
                    print r.status_code

            else:
                print "登录不需要验证码"
                # 不需要验证码的逻辑 和 上面输入验证码之后 的 逻辑 一样
                # 此处代码省略
        else:
            print "login fail", r.status_code
        pass

if __name__ == "__main__":
    t = DouBan()
    t.login()
    pass

2、httpx 的使用

pypi :https://pypi.org/project/httpx/

安装:pip install httpx

httpx 是 Python3 的一个功能齐全的 HTTP 客户端库。它包括一个集成的命令行客户端,支持HTTP/1.1 和 HTTP/2,并提供 同步和异步 api。HTTPX 目标是与 requests 库的 API 广泛兼容

HTTPX 介绍

示例:

import httpx

r = httpx.get('https://www.example.org/')
print(r.status_code)
print(r.headers['content-type'])
print(r.text)

命令行:pip install 'httpx[cli]'

示例:

import httpx
from PIL import Image
from io import BytesIO


r = httpx.get('https://httpbin.org/get')
r = httpx.post('https://httpbin.org/post', data={'key': 'value'})
r = httpx.put('https://httpbin.org/put', data={'key': 'value'})
r = httpx.delete('https://httpbin.org/delete')
r = httpx.head('https://httpbin.org/get')
r = httpx.options('https://httpbin.org/get')

headers = {'user-agent': 'my-app/0.0.1'}
params = {'key1': 'value1', 'key2': 'value2'}
r = httpx.get('https://httpbin.org/get', params=params, headers=headers)
print(r.text)
print(r.content)
print(r.json())
print(r.encoding)
r.encoding = 'utf-8'

# 从二进制流中创建图片
i = Image.open(BytesIO(r.content))

# 上传文件
files = {'upload-file': open('report.xls', 'rb')}
r = httpx.post("https://httpbin.org/post", files=files)
print(r.text)
# 也可以以原则的形式,显式地设置文件名和内容类型
files = {'upload-file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel')}
r = httpx.post("https://httpbin.org/post", files=files)

快速开始

高级用法

client 实例

如果用过 requests 库,那么 httpx.Client() 就相当于 requests.Session()

为什么要使用客户端?

  • 如果在快速入门中使用顶级 API 发出请求时,HTTPX 必须为每个请求建立新连接(不会重复使用连接)。随着对主机的请求数量的增加,这很快就会变得低效。
  • "Client实例" 使用 HTTP 连接池。这意味着,当您向同一主机发出多个请求时,将重用基础 TCP 连接,而不是为每个请求重新创建一个。

与使用顶级 API 相比,这可以带来显著的性能改进,包括:

  • 减少了跨请求的延迟(无握手)。
  • 减少了 CPU 使用率和往返。
  • 减少网络拥塞。

额外功能

Client实例还支持顶级 API 中不可用的功能,例如:

  • 跨请求的 Cookie 持久性。
  • 对所有传出请求应用配置。
  • 通过 HTTP 代理发送请求。
  • 使用 HTTP/2

用法:( 推荐使用 with 方式 )

with httpx.Client() as client:
    ...

或者,手动显式关闭连接池:
client = httpx.Client()
try:
    ...
finally:
    client.close()

创建完 client 后,就可以使用 client.get、client .post 等方法来发送请求,参数和httpx.get(),httpx.post() 等都相同

跨请求共享配置

通过给 client 构造函数传递参数,可以在所有传出的请求间共享配置。

class Client(BaseClient):
    """
    An HTTP client, with connection pooling, HTTP/2, redirects, cookie persistence, etc.

    It can be shared between threads.

    Usage:

    ```python
    >>> client = httpx.Client()
    >>> response = client.get('https://example.org')
    ```

    **Parameters:**

    * **auth** - *(optional)* An authentication class to use when sending
    requests.
    * **params** - *(optional)* Query parameters to include in request URLs, as
    a string, dictionary, or sequence of two-tuples.
    * **headers** - *(optional)* Dictionary of HTTP headers to include when
    sending requests.
    * **cookies** - *(optional)* Dictionary of Cookie items to include when
    sending requests.
    * **verify** - *(optional)* SSL certificates (a.k.a CA bundle) used to
    verify the identity of requested hosts. Either `True` (default CA bundle),
    a path to an SSL certificate file, an `ssl.SSLContext`, or `False`
    (which will disable verification).
    * **cert** - *(optional)* An SSL certificate used by the requested host
    to authenticate the client. Either a path to an SSL certificate file, or
    two-tuple of (certificate file, key file), or a three-tuple of (certificate
    file, key file, password).
    * **proxies** - *(optional)* A dictionary mapping proxy keys to proxy
    URLs.
    * **timeout** - *(optional)* The timeout configuration to use when sending
    requests.
    * **limits** - *(optional)* The limits configuration to use.
    * **max_redirects** - *(optional)* The maximum number of redirect responses
    that should be followed.
    * **base_url** - *(optional)* A URL to use as the base when building
    request URLs.
    * **transport** - *(optional)* A transport class to use for sending requests
    over the network.
    * **app** - *(optional)* An WSGI application to send requests to,
    rather than sending actual network requests.
    * **trust_env** - *(optional)* Enables or disables usage of environment
    variables for configuration.
    * **default_encoding** - *(optional)* The default encoding to use for decoding
    response text, if no charset information is included in a response Content-Type
    header. Set to a callable for automatic character set detection. Default: "utf-8".
    """

    def __init__(
        self,
        *,
        auth: typing.Optional[AuthTypes] = None,
        params: typing.Optional[QueryParamTypes] = None,
        headers: typing.Optional[HeaderTypes] = None,
        cookies: typing.Optional[CookieTypes] = None,
        verify: VerifyTypes = True,
        cert: typing.Optional[CertTypes] = None,
        http1: bool = True,
        http2: bool = False,
        proxies: typing.Optional[ProxiesTypes] = None,
        mounts: typing.Optional[typing.Mapping[str, BaseTransport]] = None,
        timeout: TimeoutTypes = DEFAULT_TIMEOUT_CONFIG,
        follow_redirects: bool = False,
        limits: Limits = DEFAULT_LIMITS,
        max_redirects: int = DEFAULT_MAX_REDIRECTS,
        event_hooks: typing.Optional[
            typing.Mapping[str, typing.List[EventHook]]
        ] = None,
        base_url: URLTypes = "",
        transport: typing.Optional[BaseTransport] = None,
        app: typing.Optional[typing.Callable[..., typing.Any]] = None,
        trust_env: bool = True,
        default_encoding: typing.Union[str, typing.Callable[[bytes], str]] = "utf-8",
    ):

夸请求 共享配置示例:共享请求头

>>> url = 'http://httpbin.org/headers'
>>> headers = {'user-agent': 'my-app/0.0.1'}
>>> with httpx.Client(headers=headers) as client:
...     r = client.get(url)
...
>>> r.json()['headers']['User-Agent']

配置 合并

在 "client级别" 和 "请求级别" 同时提供配置选项时,可能会发生以下两种情况之一:

  • 对于 headers、query parameters、cookies,这些值将组合在一起。例如:

>>> headers = {'X-Auth': 'from-client'}
>>> params = {'client_id': 'client1'}
>>> with httpx.Client(headers=headers, params=params) as client:
...     headers = {'X-Custom': 'from-request'}
...     params = {'request_id': 'request1'}
...     r = client.get('https://example.com', headers=headers, params=params)
...
>>> r.request.url
URL('https://example.com?client_id=client1&request_id=request1')
>>> r.request.headers['X-Auth']
'from-client'
>>> r.request.headers['X-Custom']
'from-request'

  • 对于所有其他参数,请求级别值优先。例如:

>>> with httpx.Client(auth=('tom', 'mot123')) as client:
...     r = client.get('https://example.com', auth=('alice', 'ecila123'))
...
>>> _, _, auth = r.request.headers['Authorization'].partition(' ')
>>> import base64
>>> base64.b64decode(auth)
b'alice:ecila123'

client 才有的配置

例如,允许您在所有传出请求前面附加一个 URL:base_url

>>> with httpx.Client(base_url='http://httpbin.org') as client:
...     r = client.get('/headers')
...
>>> r.request.url
URL('http://httpbin.org/headers')

有关所有可用客户端参数的列表,请参阅 client API 参考。

字符集编码、自动检测

import httpx
import chardet

def autodetect(content):
    return chardet.detect(content).get("encoding")

client = httpx.Client(default_encoding=autodetect)
response = client.get(...)
print(response.encoding)  
print(response.text)

调用 Python Web 应用程序

可以将 httpx client 配置为使用 WSGI 协议直接调用 Python Web 应用程序。

这对于两个主要用例特别有用:

  • 在测试用例中用作客户端。
  • 在测试期间或在开发/过渡环境中模拟外部服务。

示例:可以打上断点,查看执行流程

from flask import Flask
import httpx

app = Flask(__name__)


@app.route("/")
def hello():
    return "Hello World!"


with httpx.Client(app=app, base_url="http://testserver") as client:
    r = client.get("/")
    assert r.status_code == 200
    assert r.text == "Hello World!"

request 实例

为了最大限度地控制通过网络发送的内容,HTTPX 支持构建显式 request 实例:

request = httpx.Request("GET", "https://example.com")

将 实例 发送到网络:Request.send()

with httpx.Client() as client:
    response = client.send(request)
    ...

默认的 "参数合并" 不支持在一个方法中同时使用 client-level 和 request-level 的参数,如果想要使用,则可以使用 .build_request() 构造一个实例,使用构造的实例去用

headers = {"X-Api-Key": "...", "X-Client-ID": "ABC123"}

with httpx.Client(headers=headers) as client:
    request = client.build_request("GET", "https://api.example.com")

    print(request.headers["X-Client-ID"])  # "ABC123"

    # Don't send the API key for this particular request.
    del request.headers["X-Api-Key"]

    response = client.send(request)
    ...

事件钩子

HTTPX 允许向 client 注册“事件钩子”,这些钩子在每次发生特定类型的事件时被激活并调用

目前有两个事件挂钩:

  • request- 在请求完全准备好之后,但在发送到网络之前调用。已传递实例。request
  • response- 在从网络获取响应之后,但在将其返回给调用方之前调用。已传递实例。response

这些允许您安装客户端范围的功能,例如日志记录、监视或跟踪。

import httpx


def log_request(request):
    print(f"Request event hook: {request.method} {request.url} - Waiting for response")


def log_response(response):
    request = response.request
    print(f"Response event hook: {request.method} {request.url} - Status {response.status_code}")


client = httpx.Client(event_hooks={'request': [log_request], 'response': [log_response]})

检查和修改 已安装的钩子

client = httpx.Client() client.event_hooks['request'] = [log_request] client.event_hooks['response'] = [log_response, raise_on_4xx_5xx]

监控下载进度

如果需要监视大型响应的下载进度,可以使用响应流式处理并检查属性。response.num_bytes_downloaded

示例:

import tempfile

import httpx
from tqdm import tqdm

with tempfile.NamedTemporaryFile() as download_file:
    url = "https://speed.hetzner.de/100MB.bin"
    with httpx.stream("GET", url) as response:
        total = int(response.headers["Content-Length"])

        with tqdm(total=total, unit_scale=True, unit_divisor=1024, unit="B") as progress:
            num_bytes_downloaded = response.num_bytes_downloaded
            for chunk in response.iter_bytes():
                download_file.write(chunk)
                progress.update(response.num_bytes_downloaded - num_bytes_downloaded)
                num_bytes_downloaded = response.num_bytes_downloaded
pass

示例:

import tempfile
import httpx
import rich.progress

with tempfile.NamedTemporaryFile() as download_file:
    url = "https://speed.hetzner.de/100MB.bin"
    with httpx.stream("GET", url) as response:
        total = int(response.headers["Content-Length"])

        with rich.progress.Progress(
            "[progress.percentage]{task.percentage:>3.0f}%",
            rich.progress.BarColumn(bar_width=None),
            rich.progress.DownloadColumn(),
            rich.progress.TransferSpeedColumn(),
        ) as progress:
            download_task = progress.add_task("Download", total=total)
            for chunk in response.iter_bytes():
                download_file.write(chunk)
                progress.update(download_task, completed=response.num_bytes_downloaded)

监控上传进度

import io
import random

import httpx
from tqdm import tqdm


def gen():
    """
    this is a complete example with generated random bytes.
    you can replace `io.BytesIO` with real file object.
    """
    total = 32 * 1024 * 1024  # 32m
    with tqdm(ascii=True, unit_scale=True, unit='B', unit_divisor=1024, total=total) as bar:
        with io.BytesIO(random.randbytes(total)) as f:
            while data := f.read(1024):
                yield data
                bar.update(len(data))


httpx.post("https://httpbin.org/post", content=gen())

"HTTP、SOCKS" 代理

proxies = {
    "http://": "http://localhost:8030",
    "https://": "http://localhost:8031",
}

HTTPX 支持通过在 client 初始化时传递 proxies 参数。               
        with httpx.Client(proxies=proxies) as client:
            ...
也支持顶级 API 函数传递 proxies 参数。
        httpx.get(..., proxies=...)

HTTPX 提供了细粒度控件,用于决定哪些请求必须通过proxy,哪些请求不能通过proxy。

proxies = {
    "http://": "http://username:password@localhost:8030",
    # ...
}
proxies 字典通过 "键值对"的形式映射 "url 和 proxy "。
HTTPX 将请求的 URL 与 "keys" 进行匹配,以决定应使用哪个 proxy(如果有)。
匹配是从最具体的 "keys" (例如:https://<domain>:<port>) 到最不具体 keys (例如:https://)

HTTPX 支持基于 "scheme、domain、port" 或这些方案的自由组合。

一个复杂 proxy 的配置示例,可以组合上述路由功能来构建复杂的路由配置。例如

SOCKS

超时配置

为单个请求设置超时:

# Using the top-level API:
httpx.get('http://example.com/api/v1/example', timeout=10.0)

# Using a client instance:
with httpx.Client() as client:
    client.get("http://example.com/api/v1/example", timeout=10.0)

或者禁用单个请求的超时:

# Using the top-level API:
httpx.get('http://example.com/api/v1/example', timeout=None)

# Using a client instance:
with httpx.Client() as client:
    client.get("http://example.com/api/v1/example", timeout=None)

client = httpx.Client()              # Use a default 5s timeout everywhere.
client = httpx.Client(timeout=10.0)  # Use a default 10s timeout everywhere.
client = httpx.Client(timeout=None)  # Disable all timeouts by default.

池 限制配置

可以在 client 上使用 limits 关键字参数控制连接池大小。

  • max_keepalive_connections、允许的保持活动连接数或始终 允许。(默认值 20)None
  • max_connections、允许的最大连接数或无限制。 (默认 100)None
  • keepalive_expiry、空闲保持活动连接的时间限制(以秒为单位)或无限制。(默认 5)None

limits = httpx.Limits(max_keepalive_connections=5, max_connections=10)
client = httpx.Client(limits=limits)

多部分文件编码

>>> files = {'upload-file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel')}
>>> r = httpx.post("https://httpbin.org/post", files=files)
>>> print(r.text)
{
  ...
  "files": {
    "upload-file": "<... binary content ...>"
  },
  ...
}

更具体地说,如果将元组用作值,它必须具有 2 到 3 个元素:

  • 第一个元素是可选文件名,可以设置为 。None
  • 第二个元素可以是类似文件的对象或字符串,它们将自动 以 UTF-8 编码。
  • 可选的第三个元素可用于指定要上传的文件的 MIME 类型。如果未指定,HTTPX 将尝试猜测基于 MIME 类型 在文件名上,未知文件扩展名默认为“应用程序/八位字节流”。 如果文件名显式设置为 则 HTTPX 将不包含内容类型 MIME 标头字段。None

>>> files = {'upload-file': (None, 'text content', 'text/plain')}
>>> r = httpx.post("https://httpbin.org/post", files=files)
>>> print(r.text)
{
  ...
  "files": {},
  "form": {
    "upload-file": "text-content"
  },
  ...
}

自定义身份验证

SSL 证书

使用自定义 CA ,使用 verify 参数。

import httpx

r = httpx.get("https://example.org", verify="path/to/client.pem")

或者,可以传递一个 标准库 .ssl.SSLContext

>>> import ssl
>>> import httpx
>>> context = ssl.create_default_context()
>>> context.load_verify_locations(cafile="/tmp/client.pem")
>>> httpx.get('https://example.org', verify=context)
<Response [200 OK]>

禁用SSL

import httpx
r = httpx.get("https://example.org", verify=False)

自定义 传输

HTTPX 的 Client 也接收一个 transport 参数,This argument allows you to provide a custom Transport object that will be used to perform the actual sending of the requests.

用法:对于某些高级配置,您可能需要实例化传输 类,并将其传递给客户端实例。一个例子是只能通过这个低级 API 提供 local_address 的配置。

>>> import httpx
>>> transport = httpx.HTTPTransport(local_address="0.0.0.0")
>>> client = httpx.Client(transport=transport)

也可以通过此接口进行连接重试

>>> import httpx
>>> transport = httpx.HTTPTransport(retries=1)
>>> client = httpx.Client(transport=transport)

实例化传输时,只能通过低级 API 提供的 Unix 域套接字进行连接 这一种方式。

>>> import httpx
>>> # Connect to the Docker API via a Unix Socket.
>>> transport = httpx.HTTPTransport(uds="/var/run/docker.sock")
>>> client = httpx.Client(transport=transport)
>>> response = client.get("http://docker/info")
>>> response.json()
{"ID": "...", "Containers": 4, "Images": 74, ...}

使用 urllib3 进行传输

>>> import httpx
>>> from urllib3_transport import URLLib3Transport
>>> client = httpx.Client(transport=URLLib3Transport())
>>> client.get("https://example.org")
<Response [200 OK]>

编写自定义传输

传输实例必须实现低级传输 API,该 API 处理 发送单个请求并返回响应。你要么是 httpx.BaseTransport子类,要么是 httpx.AsyncBaseTransport子类。

在传输 API 层,我们使用熟悉的 Request 和 Response 模型

有关更多详细信息,请参阅 handle_request 和 handle_async_request 文档字符串 关于传输 API 的细节。

自定义传输实现的完整示例如下:

import json
import httpx


class HelloWorldTransport(httpx.BaseTransport):
    """
    A mock transport that always returns a JSON "Hello, world!" response.
    """

    def handle_request(self, request):
        message = {"text": "Hello, world!"}
        content = json.dumps(message).encode("utf-8")
        stream = httpx.ByteStream(content)
        headers = [(b"content-type", b"application/json")]
        return httpx.Response(200, headers=headers, stream=stream)

以相同的方式使用:

>>> import httpx
>>> client = httpx.Client(transport=HelloWorldTransport())
>>> response = client.get("https://example.org/")
>>> response.json()
{"text": "Hello, world!"}

模拟 传输

在测试过程中,能够模拟传输通常很有用, 并返回预先确定的响应,而不是发出实际的网络请求。httpx.MockTransport 类接受可以使用的处理程序函数,要将请求映射到预先确定的响应。

def handler(request):
    return httpx.Response(200, json={"text": "Hello, world!"})


# Switch to a mock transport, if the TESTING environment variable is set.
if os.environ.get('TESTING', '').upper() == "TRUE":
    transport = httpx.MockTransport(handler)
else:
    transport = httpx.HTTPTransport()

client = httpx.Client(transport=transport)

对于更高级的用例,您可能需要查看第三方 mocking library、RESPX 或 pytest-httpx library

安装运输

还可以针对给定的方案或域挂载传输,以控制 传出请求应通过具有相同样式的传输进行路由 用于指定代理路由

import httpx

class HTTPSRedirectTransport(httpx.BaseTransport):
    """
    A transport that always redirects to HTTPS.
    """

    def handle_request(self, method, url, headers, stream, extensions):
        scheme, host, port, path = url
        if port is None:
            location = b"https://%s%s" % (host, path)
        else:
            location = b"https://%s:%d%s" % (host, port, path)
        stream = httpx.ByteStream(b"")
        headers = [(b"location", location)]
        extensions = {}
        return 303, headers, stream, extensions


# A client where any `http` requests are always redirected to `https`
mounts = {'http://': HTTPSRedirectTransport()}
client = httpx.Client(mounts=mounts)

关于如何利用安装运输工具的其他一些草图......

在单个给定域上禁用 HTTP/2...

mounts = {
    "all://": httpx.HTTPTransport(http2=True),
    "all://*example.org": httpx.HTTPTransport()
}
client = httpx.Client(mounts=mounts)

模拟对给定域的请求:

# All requests to "example.org" should be mocked out.
# Other requests occur as usual.
def handler(request):
    return httpx.Response(200, json={"text": "Hello, World!"})

mounts = {"all://example.org": httpx.MockTransport(handler)}
client = httpx.Client(mounts=mounts)

添加对自定义方案的支持:

# Support URLs like "file:///Users/sylvia_green/websites/new_client/index.html"
mounts = {"file://": FileSystemTransport()}
client = httpx.Client(mounts=mounts)

使用 指南

异步

协程,英文叫作 coroutine,又称微线程、纤程,是一种运行在用户态的轻量级线程。
协程拥有自己的寄存器上下文和栈。协程在调度切换时,将寄存器上下文和栈保存到其他地方,等切回来的时候。再恢复先前保存的寄存器上下文和栈。因此,协程能保留上一次调用时的状态,即所有局部状态的一个特定组合,每次过程重人,就相当于进人上一次调用的状态
协程本质上是个单进程,相对于多进程来说,它没有线程上下文切换的开销,没有原子操作锁定及同步的开销,编程模型也非常简单。
可以使用协程来实现异步操作,例如在网络爬虫场景下,我们发出一个请求之后,需要等待一定时间才能得到响应,但其实在这个等待过程中,程序可以干许多其他事情,等得到响应之后再切换回来继续处理,这样可以充分利用CPU和其他资源,这就是协程的优势

发出异步请求,需要使用 .AsyncClient

>>> async with httpx.AsyncClient() as client:
...     r = await client.get('https://www.example.com/')
...
>>> r
<Response [200 OK]>

使用格式 :response = await client.get(...)

  • AsyncClient.get(url, ...)
  • AsyncClient.options(url, ...)
  • AsyncClient.head(url, ...)
  • AsyncClient.post(url, ...)
  • AsyncClient.put(url, ...)
  • AsyncClient.patch(url, ...)
  • AsyncClient.delete(url, ...)
  • AsyncClient.request(method, url, ...)
  • AsyncClient.send(request, ...)

异步上下文

注意:为了从连接池中获得最大好处,请不要实例化多个 client 实例 。 例如,通过使用 async with 可以创建只有一个单个作用域的 client 实例,在该 client 实例中传递任何需要的参数。

async with httpx.AsyncClient() as client:
    ...

或者,显式关闭客户端:await client.aclose()

client = httpx.AsyncClient()
...
await client.aclose()

示例:

import asyncio
import httpx
import datetime

pool_size_limit = httpx.Limits(max_keepalive_connections=300, max_connections=500)


async def fetch(url=None):
    async with httpx.AsyncClient(limits=pool_size_limit) as client:
        resp = await client.get('https://www.example.com/')
        print(resp.status_code)


async def main():
    url = 'https://www.httpbin.org/delay/5'
    task_list = []
    for index in range(100):
        task_list.append(asyncio.create_task(fetch(url)))
    await asyncio.wait(task_list)


if __name__ == '__main__':
    time_1 = datetime.datetime.now()
    asyncio.run(main())
    time_2 = datetime.datetime.now()
    print((time_2 - time_1).seconds)

流式 处理 响应

该方法是一个异步上下文块。AsyncClient.stream(method, url, ...)

>>> client = httpx.AsyncClient()
>>> async with client.stream('GET', 'https://www.example.com/') as response:
...     async for chunk in response.aiter_bytes():
...      

异步响应流式处理方法包括:

  • Response.aread()- 用于有条件地读取流块内的响应。
  • Response.aiter_bytes()- 用于将响应内容流式传输为字节。
  • Response.aiter_text()- 用于将响应内容流式传输为文本。
  • Response.aiter_lines()- 用于将响应内容流式传输为文本行。
  • Response.aiter_raw()- 用于流式传输原始响应字节,而无需应用内容解码。
  • Response.aclose()- 用于关闭响应。通常不需要它,因为 stream block 会在退出时自动关闭响应。

对于不能使用上下文块进行处理的情况,可以通过使用 发送请求实例来进入“手动模式”。client.send(..., stream=True)

使用 Starlette 将响应转发到流式处理 Web 终结点的上下文中的示例:

import httpx
from starlette.background import BackgroundTask
from starlette.responses import StreamingResponse

client = httpx.AsyncClient()

async def home(request):
    req = client.build_request("GET", "https://www.example.com/")
    r = await client.send(req, stream=True)
    return StreamingResponse(r.aiter_text(), background=BackgroundTask(r.aclose))

流式 处理 请求

async def upload_bytes():
    ...  # yield byte content

await client.post(url, content=upload_bytes())

显式 传输 实例

直接实例化传输实例时,需要使用 .httpx.AsyncHTTPTransport

>>> import httpx
>>> transport = httpx.AsyncHTTPTransport(retries=1)
>>> async with httpx.AsyncClient(transport=transport) as client:
>>>     ...

支持的异步环境

HTTPX 支持 asyncio 或 trio 作为异步环境。它将自动检测这两者中的哪一个用作后端 用于套接字操作和并发基元。

AsyncIO是Python的内置库,用于编写具有async/await语法的并发代码。

import asyncio
import httpx

async def main():
    async with httpx.AsyncClient() as client:
        response = await client.get('https://www.example.com/')
        print(response)

asyncio.run(main())

Trio 是一个替代的异步库, 围绕结构化并发原则设计。

import httpx
import trio

async def main():
    async with httpx.AsyncClient() as client:
        response = await client.get('https://www.example.com/')
        print(response)

trio.run(main)

AnyIO ( 地址:https://github.com/agronholm/anyio )。AnyIO 是一个异步网络和并发库,它工作在 asyncio 或 trio 之上。它与所选后端的本机库混合在一起(默认为 asyncio)。

import httpx
import anyio

async def main():
    async with httpx.AsyncClient() as client:
        response = await client.get('https://www.example.com/')
        print(response)

anyio.run(main, backend='trio')

调用 Python Web 应用程序

就像httpx.Client允许您直接调用WSGI Web应用程序一样, httpx.AsyncClient允许您直接调用 ASGI Web 应用程序。

以 Starlette 应用程序为例:

from starlette.applications import Starlette
from starlette.responses import HTMLResponse
from starlette.routing import Route


async def hello(request):
    return HTMLResponse("Hello World!")


app = Starlette(routes=[Route("/", hello)])

可以直接针对应用程序发出请求,如下所示:

>>> import httpx
>>> async with httpx.AsyncClient(app=app, base_url="http://testserver") as client:
...     r = await client.get("/")
...     assert r.status_code == 200
...     assert r.text == "Hello World!"

启用 HTTP/2

HTTP / 2是HTTP协议的主要新版本,它提供了更多的 高效运输,具有潜在的性能优势。HTTP/2 不会改变 请求或响应的核心语义,但改变了数据的方式 发送到服务器和从服务器发送。

HTTP/1 不是 HTTP/1.2 使用的文本格式,而是二进制格式。 二进制格式提供完整的请求和响应多路复用,并且高效 压缩 HTTP 标头。流多路复用意味着HTTP / 1.1 每个并发请求需要一个TCP流,HTTP / 2允许单个TCP 流以处理多个并发请求。

HTTP/2 还提供对响应优先级、 和服务器推送。

有关HTTP / 2的综合指南,您可能需要查看“http2解释”。

使用 httpx client 时,默认情况下不启用 HTTP/2 支持,因为 HTTP/1.1 是一个成熟的、久经沙场的传输层,所以现在HTTP/1.1是更好的选择。 未来版本的 httpx 可能会默认启用 HTTP/2 支持.

首先确保安装 可选的 HTTP/2 依赖项:pip install httpx[http2]
然后实例化启用了 HTTP/2 支持的客户端:client = httpx.AsyncClient(http2=True)

async with httpx.AsyncClient(http2=True) as client:
    ...

在客户端上启用 HTTP/2 支持并不一定意味着您的 请求和响应将通过 HTTP/2 传输,因为客户端服务器都需要支持 HTTP/2。如果连接到仅 支持 HTTP/1.1 客户端将使用标准的 HTTP/1.1 连接。

检查 http 版本

client = httpx.AsyncClient(http2=True)
response = await client.get(...)
print(response.http_version)  # "HTTP/1.0", "HTTP/1.1", or "HTTP/2".

API 引用

API 接口

异 常

异常层次结构

  • HTTPError
    • RequestError
      • TransportError
        • TimeoutException
          • ConnectTimeout
          • ReadTimeout
          • WriteTimeout
          • PoolTimeout
        • NetworkError
          • ConnectError
          • ReadError
          • WriteError
          • CloseError
        • ProtocolError
          • LocalProtocolError
          • RemoteProtocolError
        • ProxyError
        • UnsupportedProtocol
      • DecodingError
      • TooManyRedirects
    • HTTPStatusError
  • InvalidURL
  • CookieConflict
  • StreamError
    • StreamConsumed
    • ResponseNotRead
    • RequestNotRead
    • StreamClosed

3、pycurl

pycurl 文档:http://pycurl.io/docs/latest/
gevent 结合 pycurl:https://bitbucket.org/denis/gevent-curl/src/default/
不过这个源码在最新版本的gevent下无法运行,修复后的版本在这里:https://github.com/dytttf/gevent-pycurl

pycurl 简介

PycURL 是 libcurl(多协议文件传输库)的 Python 接口。与 urllib Python 模块类似,PycURL 可用于从 Python 程序中获取由 URL 标识的对象。然而,除了简单的获取之外,PycURL 还公开了 libcurl 的大部分功能,包括:

  • PycURL 是在 libcurl 之上的薄包装器,其实就是调用的C语言的 libcurl 库。速度非常快
  • 功能包括多种协议支持、SSL、身份验证和代理选项。PycURL 支持大多数 libcurl 的回调。
  • 用于网络操作的套接字,PycURL 可以集成到程序的 I/O 循环中(例如,使用 Tornado)。

Requests 与 PycURL 比较

  • PycURL 可以比 Requests 快几倍。当执行多个请求并重用连接时,性能差异会更大。
  • PycURL 可以通过 libcurl multi 接口利用 I/O 多路复用。
  • PycURL 支持许多协议,而不仅仅是 HTTP。
  • PycURL 通常提供更多功能,例如能够使用多个 TLS 后端、更多身份验证选项等。
  • Requests 是用纯 Python 编写的,不需要 C 扩展。因此,Requests 的安装非常简单,而 PycURL 的安装可能很复杂
  • Requests 的 API 通常比 PycURL 的 API 更易于学习和使用。

关于 libcurl

  • libcurl 是一个免费且易于使用的客户端 URL 传输库,支持 DICT、FILE、FTP、FTPS、Gopher、HTTP、HTTPS、IMAP、IMAPS、LDAP、LDAPS、POP3、POP3S、RTMP、RTSP、SCP、SFTP、SMTP、SMTPS、Telnet 和 TFTP。libcurl 支持 SSL 证书、HTTP POST、HTTP PUT、FTP 上传、基于 HTTP 表单的上传、代理、cookie、用户 + 密码认证(Basic、Digest、NTLM、Negotiate、Kerberos4)、文件传输恢复、http 代理隧道等!
  • libcurl 是免费的线程安全的IPv6 兼容的、功能丰富的支持的、快速的、有完整文档的,并且已经被许多知名的、大的和成功的公司和众多应用程序使用。

要求:libcurl 7.19.0 或更高版本。目前没有官方的二进制 Windows 包。可以从源代码构建 PycURL,也可以使用第三方二进制包。

linux 安装 curl: yum install curl
Python 安装模块:pip install pycurl

快速入门

快速入门:http://pycurl.io/docs/latest/quickstart.html

pycurl 的使用方法

c.setopt(pycurl.URL,myurl)            #(网址)
c.setopt(c.HTTPHEADER, http_header)   #网址头部
c.setopt(c.POST, 1)                   #1表示调用post方法而不是get
c.setopt(pycurl.POSTFIELDS,data)      #数据
c.setopt(pycurl.WRITEFUNCTION,my_func)#返回数据,进行回调
c.setopt(pycurl.CONNECTTIMEOUT,60)    #超时中断
c.setopt(pycurl.TIMEOUT,600)          #下载超时
c.perform()                           #提交

常用方法:

pycurl.Curl()                                               # 创建一个pycurl对象的方法
pycurl.Curl().setopt(pycurl.URL, http://www.pythontab.com)  # 设置要访问的URL
pycurl.Curl().setopt(pycurl.MAXREDIRS, 5)                   # 设置最大重定向次数
pycurl.Curl().setopt(pycurl.CONNECTTIMEOUT, 60)
pycurl.Curl().setopt(pycurl.TIMEOUT, 300)                   # 连接超时设置
 
# 模拟浏览器
pycurl.Curl().setopt(pycurl.USERAGENT, "Mozilla/5.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)") 
 
pycurl.Curl().perform()                 # 服务器端返回的信息
pycurl.Curl().getinfo(pycurl.HTTP_CODE) # 查看HTTP的状态 类似urllib中status属性
pycurl.NAMELOOKUP_TIME                  # 域名解析时间
pycurl.CONNECT_TIME                     # 远程服务器连接时间
pycurl.PRETRANSFER_TIME                 # 连接上后到开始传输时的时间
pycurl.STARTTRANSFER_TIME               # 接收到第一个字节的时间
pycurl.TOTAL_TIME                       # 上一请求总的时间
pycurl.REDIRECT_TIME                    # 如果存在转向的话,花费的时间
pycurl.EFFECTIVE_URL
pycurl.HTTP_CODE HTTP                   # 响应代码
pycurl.REDIRECT_COUNT                   # 重定向的次数
pycurl.SIZE_UPLOAD                      # 上传的数据大小
pycurl.SIZE_DOWNLOAD                    # 下载的数据大小
pycurl.SPEED_UPLOAD                     # 上传速度
pycurl.HEADER_SIZE                      # 头部大小
pycurl.REQUEST_SIZE                     # 请求大小
pycurl.CONTENT_LENGTH_DOWNLOAD          # 下载内容长度
pycurl.CONTENT_LENGTH_UPLOAD            # 上传内容长度
pycurl.CONTENT_TYPE                     # 内容的类型
pycurl.RESPONSE_CODE                    # 响应代码
pycurl.SPEED_DOWNLOAD                   # 下载速度
pycurl.SSL_VERIFYRESULT
pycurl.INFO_FILETIME                    # 文件的时间信息
pycurl.HTTP_CONNECTCODE HTTP            # 连接代码
pycurl.HTTPAUTH_AVAIL
pycurl.PROXYAUTH_AVAIL
pycurl.OS_ERRNO
pycurl.NUM_CONNECTS
pycurl.SSL_ENGINES
pycurl.INFO_COOKIELIST
pycurl.LASTSOCKET
pycurl.FTP_ENTRY_PATH

示例 :

import StringIO
import pycurl
 
c = pycurl.Curl()
str = StringIO.StringIO()
c.setopt(pycurl.URL, "http://www.pythontab.com")
c.setopt(pycurl.WRITEFUNCTION, str.write)
c.setopt(pycurl.FOLLOWLOCATION, 1)  
c.perform()
print c.getinfo(pycurl.EFFECTIVE_URL)

4、下载 "图片、音乐、视频"

使用 requests 下载 图片

美女 图片

https://www.meitu131.com/shouji/meinv/
https://pixabay.com/zh/images/search/美女/
https://www.2meinv.cc/

# -*- coding: utf-8 -*-

import requests


def download_img():
    print("downloading with requests")
    
    # test_url = 'http://www.pythontab.com/test/demo.zip'
    # r = requests.get(test_url)
    # with open("./demo.zip", "wb") as ff:
    #     ff.write(r.content)

    img_url = 'https://img9.doubanio.com/view/celebrity/s_ratio_celebrity/public/p28424.webp'
    r = requests.get(img_url)
    with open("./img.jpg", "wb") as ff:
        ff.write(r.content)


if __name__ == '__main__':
    download_img()

爬取 校花网:http://www.xueshengmai.com/hua/ 大学校花 的图片
Python使用Scrapy爬虫框架全站爬取图片并保存本地(@妹子图@):https://www.cnblogs.com/william126/p/6923017.html

单线程版本

# -*- coding: utf-8 -*-

import os
import requests
# from PIL import Image
from lxml import etree


class Spider(object):
    """ crawl image """

    def __init__(self):
        self.index = 0
        self.url = "http://www.xueshengmai.com"
        # self.proxies = {
        #     "http": "http://172.17.18.80:8080",
        #     "https": "https://172.17.18.80:8080"
        # }
        pass

    def download_image(self, image_url):
        real_url = self.url + image_url
        print("downloading the {0} image".format(self.index))
        with open("./{0}.jpg".format(self.index), 'wb') as f:
            self.index += 1
            try:
                r = requests.get(
                    real_url,
                    # proxies=self.proxies
                )
                if 200 == r.status_code:
                    f.write(r.content)
            except BaseException as e:
                print(e)
        pass

    def add_url_prefix(self, image_url):
        return self.url + image_url

    def start_crawl(self):
        start_url = "http://www点xueshengmai点com/hua/"
        r = requests.get(
            start_url,
            # proxies=self.proxies
        )
        if 200 == r.status_code:
            temp = r.content.decode("gbk")
            html = etree.HTML(temp)
            links = html.xpath('//div[@class="item_t"]//img/@src')

            # url_list = list(map(lambda image_url=None: self.url + image_url, links))

            ###################################################################
            # python2
            # map(self.download_image, links)

            # python3 返回的是一个 map object ,所以需要 使用 list 包括下
            list(map(self.download_image, links))
            ###################################################################

            next_page_url = html.xpath(u'//div[@class="page_num"]//a[contains(text(),"下一页")]/@href')
            page_num = 2
            while next_page_url:
                print("download {0} page images".format(page_num))
                r_next = requests.get(
                    next_page_url[0],
                    # proxies=self.proxies
                )
                if r_next.status_code == 200:
                    html = etree.HTML(r_next.content.decode("gbk"))
                    links = html.xpath('//div[@class="item_t"]//img/@src')

                    # python3 返回的是一个 map object ,所以需要 使用 list 包括下
                    list(map(self.download_image, links))

                    try:
                        t_x_string = u'//div[@class="page_num"]//a[contains(text(),"下一页")]/@href'
                        next_page_url = html.xpath(t_x_string)
                    except BaseException as e:
                        next_page_url = None
                        # print e
                    page_num += 1
                    pass
                else:
                    print("response status code : {0}".format(r_next.status_code))
                pass
        else:
            print("response status code : {0}".format(r.status_code))
        pass


if __name__ == "__main__":
    t = Spider()
    t.start_crawl()
    pause = input("press any key to continue")
    pass

抓取 "妹子图"  代码:

# coding=utf-8

import requests
import os
from lxml import etree
import sys

'''
reload(sys)
sys.setdefaultencoding('utf-8')
'''


platform = 'Windows' if os.name == 'nt' else 'Linux'
print(f'当前系统是 【{platform}】 系统')

# http请求头
header = {
    # ':authority': 'www点mzitu点com',
    # ':method': 'GET',
    'accept': '*/*',
    'accept-encoding': 'gzip, deflate, br',
    'referer': 'https://www点mzitu点com',
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                  'Chrome/75.0.3770.90 Safari/537.36'
}

site_url = 'http://www点mzitu点com'
url_prefix = 'http://www点mzitu点com/page/'
img_save_path = 'C:/mzitu/'


def get_page_max_num(page_html=None, flag=1):
    """

    :param page_html: 页面的 HTML 文本
    :param flag:  表示是 那个页面,1:所有妹子的列表页面。2:每个妹子单独的图片页面。
    :return:
    """
    # 找寻最大页数
    s_html = etree.HTML(page_html)

    xpath_string = '//div[@class="nav-links"]//a' if 1 == flag \
        else '//div[@class="pagenavi"]//a//span'

    display_page_link = s_html.xpath(xpath_string)
    # print(display_page_link[-1].text)
    max_num = display_page_link[-2].text if '下一页»' == display_page_link[-1].text \
        else display_page_link[-1].text
    return int(max_num)


def main():
    site_html = requests.get(site_url, headers=header).text
    page_max_num_1 = get_page_max_num(site_html)
    for page_num in range(1, page_max_num_1 + 1):
        page_url = f'{url_prefix}{page_num}'
        page_html = requests.get(page_url, headers=header).text
        s_page_html = etree.HTML(text=page_html)
        every_page_mm_url_list = s_page_html.xpath(
            '//ul[@id="pins"]//li[not(@class="box")]/span/a'
        )

        for tag_a in every_page_mm_url_list:
            mm_url = tag_a.get('href')
            title = tag_a.text.replace('\\', '').replace('/', '').replace(':', '')
            title = title.replace('*', '').replace('?', '').replace('"', '')
            title = title.replace('<', '').replace('>', '').replace('|', '')

            mm_dir = f'{img_save_path}{title}'
            if not os.path.exists(mm_dir):
                os.makedirs(mm_dir)

            print(f'【{title}】开始下载')
            mm_page_html = requests.get(mm_url, headers=header).text
            mm_page_max_num = get_page_max_num(mm_page_html, flag=2)
            for index in range(1, mm_page_max_num + 1):
                photo_url = f'{mm_url}/{index}'
                photo_html = requests.get(photo_url, headers=header).text
                s_photo_html = etree.HTML(text=photo_html)
                img_url = s_photo_html.xpath('//div[@class="main-image"]//img')[0].get('src')
                # print(img_url)
                r = requests.get(img_url, headers=header)
                if r.status_code == 200:
                    with open(f'{mm_dir}/{index}.jpg', 'wb') as f:
                        f.write(r.content)
                else:
                    print(f'status code : {r.status_code}')
            else:
                print(f'【{title}】下载完成')
        print(f'第【{page_num}】页完成')


if __name__ == '__main__':
    main()
    pass

运行成功后,会在脚本所在的目录 生成对应目录,每个目录里面都有对应的图片。。。。。

多线程版本。从 Python3.2开始,Python 标准库提供了 concurrent.futures 模块, concurrent.futures 模块可以利用 multiprocessing 实现真正的平行计算。python3 自带,python2 需要安装。

# coding=utf-8

import requests
import os
from lxml import etree
import sys
from concurrent import futures

'''
reload(sys)
sys.setdefaultencoding('utf-8')
'''

platform = 'Windows' if os.name == 'nt' else 'Linux'
print(f'当前系统是 【{platform}】 系统')


# http请求头
header = {
    # ':authority': 'www点mzitu点com',
    # ':method': 'GET',
    'accept': '*/*',
    'accept-encoding': 'gzip, deflate, br',
    'referer': 'https://www点mzitu点com',
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
                  '(KHTML, like Gecko) Chrome/75.0.3770.90 Safari/537.36'                  
}


site_url = 'http://www点mzitu点com'
url_prefix = 'http://www点mzitu点com/page/'
img_save_path = 'C:/mzitu/'


def get_page_max_num(page_html=None, flag=1):
    """
    :param page_html: 页面的 HTML 文本
    :param flag:  表示是 那个页面,1:所有妹子的列表页面。2:每个妹子单独的图片页面。
    :return:
    """
    # 找寻最大页数
    s_html = etree.HTML(page_html)

    xpath_string = '//div[@class="nav-links"]//a' if 1 == flag \
        else '//div[@class="pagenavi"]//a//span'

    display_page_link = s_html.xpath(xpath_string)
    # print(display_page_link[-1].text)
    max_num = display_page_link[-2].text if '下一页»' == display_page_link[-1].text \
        else display_page_link[-1].text
    return int(max_num)


def download_img(args_info):
    img_url, mm_dir, index = args_info
    r = requests.get(img_url, headers=header)
    if r.status_code == 200:
        with open(f'{mm_dir}/{index}.jpg', 'wb') as f:
            f.write(r.content)
    else:
        print(f'status code : {r.status_code}')


def main():
    # 线程池中线程数
    with futures.ProcessPoolExecutor() as process_pool_executor:
        site_html = requests.get(site_url, headers=header).text
        page_max_num_1 = get_page_max_num(site_html)
        for page_num in range(1, page_max_num_1 + 1):
            page_url = f'{url_prefix}{page_num}'
            page_html = requests.get(page_url, headers=header).text
            s_page_html = etree.HTML(text=page_html)
            every_page_mm_url_list = s_page_html.xpath(
                '//ul[@id="pins"]//li[not(@class="box")]/span/a'
            )

            for tag_a in every_page_mm_url_list:
                mm_url = tag_a.get('href')
                title = tag_a.text.replace('\\', '').replace('/', '').replace(':', '')
                title = title.replace('*', '').replace('?', '').replace('"', '')
                title = title.replace('<', '').replace('>', '').replace('|', '')

                mm_dir = f'{img_save_path}{title}'
                if not os.path.exists(mm_dir):
                    os.makedirs(mm_dir)

                print(f'【{title}】开始下载')
                mm_page_html = requests.get(mm_url, headers=header).text
                mm_page_max_num = get_page_max_num(mm_page_html, flag=2)
                for index in range(1, mm_page_max_num + 1):
                    photo_url = f'{mm_url}/{index}'
                    photo_html = requests.get(photo_url, headers=header).text
                    s_photo_html = etree.HTML(text=photo_html)
                    img_url = s_photo_html.xpath('//div[@class="main-image"]//img')[0].get('src')

                    # 提交一个可执行的回调 task,它返回一个 Future 对象
                    process_pool_executor.submit(download_img, (img_url, mm_dir, index))
                else:
                    print(f'【{title}】下载完成')
            print(f'第【{page_num}】页完成')


if __name__ == '__main__':
    main()
    pass

显示 进度条

请求关键参数:stream=True。默认情况下,当你进行网络请求后,响应体会立即被下载。你可以通过 stream 参数覆盖这个行为,推迟下载响应体直到访问 Response.content 属性。

import json
import requests

tarball_url = 'https://github.com/kennethreitz/requests/tarball/master'

r = requests.get(tarball_url, stream=True)  # 此时仅有响应头被下载下来了,连接保持打开状态,响应体并没有下载。

print(json.dumps(dict(r.headers), ensure_ascii=False, indent=4))
# if int(r.headers['content-length']) < TOO_LONG:
#     content = r.content  # 只要访问 Response.content 属性,就开始下载响应体
#     # ...
#     pass

进一步使用 Response.iter_content 和 Response.iter_lines 方法来控制工作流,或者以 Response.raw 从底层 urllib3 的 urllib3.HTTPResponse

from contextlib import closing

with closing(requests.get('http://httpbin.org/get', stream=True)) as r:
    # Do things with the response here.
    pass

保持活动状态(持久连接) 。归功于 urllib3,同一会话内的持久连接是完全自动处理的,同一会话内发出的任何请求都会自动复用恰当的连接!注意:只有当响应体的所有数据被读取完毕时,连接才会被释放到连接池;所以确保将 stream 设置为 False 或读取 Response 对象的 content 属性。

在 Python3 中,print()方法的默认结束符(end=’\n’),当调用完之后,光标自动切换到下一行,此时就不能更新原有输出。将结束符改为 “\r” ,输出完成之后,光标会回到行首,并不换行。此时再次调用 print() 方法,就会更新这一行输出了。结束符也可以使用 “\d”,为退格符,光标回退一格,可以使用多个,按需求回退。在结束这一行输出时,将结束符改回 “\n” 或者不指定使用默认

下面是一个格式化的进度条显示模块。代码如下:

import requests
from contextlib import closing

"""
作者:微微寒
链接:https://www.zhihu.com/question/41132103/answer/93438156
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
"""


class ProgressBar(object):
    def __init__(
            self, title, count=0.0, run_status=None, fin_status=None,
            total=100.0, unit='', sep='/', chunk_size=1.0
    ):
        super(ProgressBar, self).__init__()
        self.info = "[%s] %s %.2f %s %s %.2f %s"
        self.title = title
        self.total = total
        self.count = count
        self.chunk_size = chunk_size
        self.status = run_status or ""
        self.fin_status = fin_status or " " * len(self.status)
        self.unit = unit
        self.seq = sep

    def __get_info(self):
        # 【名称】状态 进度 单位 分割线 总数 单位
        _info = self.info % (
            self.title, self.status, self.count/self.chunk_size, 
            self.unit, self.seq, self.total/self.chunk_size, self.unit
        )
        return _info

    def refresh(self, count=1, status=None):
        self.count += count
        # if status is not None:
        self.status = status or self.status
        end_str = "\r"
        if self.count >= self.total:
            end_str = '\n'
            self.status = status or self.fin_status
        print(self.__get_info(), end=end_str)


def main():
    with closing(requests.get("http://www.futurecrew.com/skaven/song_files/mp3/razorback.mp3", stream=True)) as response:
        chunk_size = 1024
        content_size = int(response.headers['content-length'])
        progress = ProgressBar(
            "razorback", total=content_size, unit="KB", 
            chunk_size=chunk_size, run_status="正在下载", fin_status="下载完成"
        )
        # chunk_size = chunk_size < content_size and chunk_size or content_size
        with open('./file.mp3', "wb") as file:
            for data in response.iter_content(chunk_size=chunk_size):
                file.write(data)
                progress.refresh(count=len(data))


if __name__ == '__main__':
    main()


Rich 是一个 Python 库,可以在终端中提供富文本和精美格式,还可以绘制漂亮的表格、进度条、markdown、语法高亮的源代码以及栈回溯信息(tracebacks)等。

github:https://github.com/textualize/rich/blob/master/README.cn.md

断点 续传

断点续传:
https://www.leavesongs.com/PYTHON/resume-download-from-break-point-tool-by-python.html

Python实现下载界面(带进度条,断点续传,多线程多任务下载):https://blog.51cto.com/eddy72/2106091
视频下载以及断点续传( 使用 aiohttp 并发 ):https://www.cnblogs.com/baili-luoyun/p/10507608.html

另一种方法是调用 curl 之类支持断点续传的下载工具。

HTTP 断点续传原理

其实 HTTP 断点续传原理比较简单,在 HTTP 数据包中,可以增加 Range 头,这个头以字节为单位指定请求的范围,来下载范围内的字节流。如:

如上图勾下来的地方,我们发送数据包时选定请求的内容的范围,返回包即获得相应长度的内容。所以,我们在下载的时候,可以将目标文件分成很多“小块”,每次下载一小块(用Range标明小块的范围),直到把所有小块下载完。

当网络中断,或出错导致下载终止时,我们只需要记录下已经下载了哪些“小块”,还没有下载哪些。下次下载的时候在Range处填写未下载的小块的范围即可,这样就能构成一个断点续传。

其实像迅雷这种多线程下载器也是同样的原理。将目标文件分成一些小块,再分配给不同线程去下载,最后整合再检查完整性即可。

先看看这段文档:Advanced Usage — Requests 2.27.1 documentation,当请求时设置steam=True的时候就不会立即关闭连接,而我们以流的形式读取body,直到所有信息读取完全或者调用Response.close关闭连接。

所以,如果要下载大文件的话,就将 steam 设置为True,慢慢下载,而不是等整个文件下载完才返回。stackoverflow上有同学给出了一个简单的下载 demo:

#!/usr/bin/env python3

import requests


def download_file(url):
    local_filename = url.split('/')[-1]
    # NOTE the stream=True parameter
    r = requests.get(url, stream=True)
    with open(local_filename, 'wb') as f:
        for chunk in r.iter_content(chunk_size=1024): 
            if chunk:  # filter out keep-alive new chunks
                f.write(chunk)
                f.flush()
    return local_filename

这基本上就是我们核心的下载代码了。

  • 当使用 requests 的 get 下载大文件/数据时,建议使用使用 stream 模式。
  • 当把 get 函数的 stream 参数设置成 False 时,它会立即开始下载文件并放到内存中,如果文件过大,有可能导致内存不足。
  • 当把 get 函数的 stream 参数设置成 True 时,它不会立即开始下载,当你使用 iter_content 或 iter_lines 遍历内容或访问内容属性时才开始下载。需要注意一点:文件没有下载之前,它也需要保持连接。

iter_content:一块一块的遍历要下载的内容
iter_lines:一行一行的遍历要下载的内容

使用上面两个函数下载大文件可以防止占用过多的内存,因为每次只下载小部分数据。

示例代码:

r = requests.get(url_file, stream=True)
f = open("file_path", "wb")
for chunk in r.iter_content(chunk_size=512):
if chunk:
f.write(chunk)

断点续传结合大文件下载

先考虑一下需要注意的有哪些点,或者可以添加的功能有哪些:

  • 1. 用户自定义性:可以定义 cookie、referer、user-agent。如某些下载站检查用户登录才允许下载等情况。header中可能有filename,url中也有filename,用户还可以自己指定filename
  • 2. 很多服务端不支持断点续传,如何判断?
  • 3. 怎么去表达进度条?
  • 4. 如何得知文件的总大小?使用HEAD请求?那么服务器不支持HEAD请求怎么办?
  • 5. 下载后的文件名怎么处理?还要考虑windows不允许哪些字符做文件名。
  • 6. 如何去分块,是否加入多线程。
def download(self, url, filename, headers = {}):
    finished = False
    block = self.config['block']
    local_filename = self.remove_nonchars(filename)
    tmp_filename = local_filename + '.downtmp'
    if self.support_continue(url):  # 支持断点续传
        try:
            with open(tmp_filename, 'rb') as fin:
                self.size = int(fin.read()) + 1
        except:
            self.touch(tmp_filename)
        finally:
            headers['Range'] = "bytes=%d-" % (self.size, )
    else:
        self.touch(tmp_filename)
        self.touch(local_filename)

    size = self.size
    total = self.total
    r = requests.get(url, stream = True, verify = False, headers = headers)
    if total > 0:
        print "[+] Size: %dKB" % (total / 1024)
    else:
        print "[+] Size: None"
    start_t = time.time()
    with open(local_filename, 'ab') as f:
        try:
            for chunk in r.iter_content(chunk_size = block): 
                if chunk:
                    f.write(chunk)
                    size += len(chunk)
                    f.flush()
                sys.stdout.write('\b' * 64 + 'Now: %d, Total: %s' % (size, total))
                sys.stdout.flush()
            finished = True
            os.remove(tmp_filename)
            spend = int(time.time() - start_t)
            speed = int(size / 1024 / spend)
            sys.stdout.write('\nDownload Finished!\nTotal Time: %ss, Download Speed: %sk/s\n' % (spend, speed))
            sys.stdout.flush()

        except:
            import traceback
            print traceback.print_exc()
            print "\nDownload pause.\n"
        finally:
            if not finished:
                with open(tmp_filename, 'wb') as ftmp:
                    ftmp.write(str(size))

这是下载的方法。首先if语句调用 self.support_continue(url) 判断是否支持断点续传。如果支持则从一个临时文件中读取当前已经下载了多少字节,如果不存在这个文件则会抛出错误,那么size默认=0,说明一个字节都没有下载。

然后就请求url,获得下载连接,for循环下载。这个时候我们得抓住异常,一旦出现异常,不能让程序退出,而是正常将当前已下载字节size写入临时文件中。下次再次下载的时候读取这个文件,将Range设置成bytes=(size+1)-,也就是从当前字节的后一个字节开始到结束的范围。从这个范围开始下载,来实现一个断点续传。

判断是否支持断点续传的方法还兼顾了一个获得目标文件大小的功能:

def support_continue(self, url):
    headers = {
        'Range': 'bytes=0-4'
    }
    try:
        r = requests.head(url, headers = headers)
        crange = r.headers['content-range']
        self.total = int(re.match(ur'^bytes 0-4/(\d+)$', crange).group(1))
        return True
    except:
        pass
    try:
        self.total = int(r.headers['content-length'])
    except:
        self.total = 0
    return False

用正则匹配出大小,获得直接获取 headers['content-length'],获得将其设置为0.

核心代码:https://github.com/phith0n/py-wget/blob/master/py-wget.py

运行程序,获取 emlog 最新的安装包:

中间我按 Ctrl + C人工打断了下载进程,但之后还是继续下载,实现了“断点续传”。

但在我实际测试过程中,并不是那么多请求可以断点续传的,所以我对于不支持断点续传的文件这样处理:重新下载。

下载后的压缩包正常解压,也充分证明了下载的完整性:

动态图演示

github 地址:一个支持断点续传的小下载器:py-wget:GitHub - phith0n/py-wget: small wget by python

使用 you-get 下载 视频

python 示例代码( you-get 多线程 下载视频 ):


import os
import subprocess
from concurrent.futures import ThreadPoolExecutor, wait


def download(url):
    video_data_dir = './vide_data_dir'
    try:
        os.makedirs(video_data_dir)
    except BaseException as be:
        pass
    video_id = url.split('/')[-1]
    video_name = f'{video_data_dir}/{video_id}'
    command = f'you-get -o ./video_data -O {video_name} ' + url
    print(command)
    subprocess.call(command, shell=True)
    print(f"退出线程 ---> {url}")


def main():
    url_list = [
        'https://www.bilibili.com/video/BV1Xz4y127Yo',
        'https://www.bilibili.com/video/BV1yt4y1Q7SS',
        'https://www.bilibili.com/video/BV1bW411n7fY',
    ]
    with ThreadPoolExecutor(max_workers=3) as pool:
        thread_id_list = [pool.submit(download, url) for url in url_list]
        wait(thread_id_list)


if __name__ == '__main__':
    main()

you-get 帮助

D:\> you-get --help
说明:一个小型的下载程序,可以抓取web数据
用法: you-get [OPTION]... URL...

可选参数:
  -V, --version         版本
  -h, --help            帮助

抓取前可以设置的参数(不会实际去下载)
  -i, --info            打印提取的信息
  -u, --url             根据提供的url,打印提取的信息
  --json                以JSON格式打印提取的url

下载参数:
  -n, --no-merge        Do not merge video parts
  --no-caption          Do not download captions (subtitles, lyrics, danmaku, ...)
  -f, --force           强制重写存在的文件
  --skip-existing-file-size-check     跳过现有文件, 并且不会检查文件大小                        
  -F STREAM_ID, --format STREAM_ID    设置视频格式为STREAM_ID                       
  -O FILE, --output-filename FILE     设置输出的文件名                        
  -o DIR, --output-dir DIR            设置输出的目录                        
  -p PLAYER, --player PLAYER          提取流中的URL到一个播放器
                        
  -c COOKIES_FILE, --cookies COOKIES_FILE  载入cookies.txt 或者 cookies.sqlite
  -t SECONDS, --timeout SECONDS  设置 socket 超时时间
  -d, --debug                    显示回溯和其他调试信息
  -I FILE, --input-file FILE     从FILE中读取非播放列表url
                        
  -P PASSWORD, --password PASSWORD
                        视频访问密码设置为password
  -l, --playlist        下载播放列表
  -a, --auto-rename     自动重命名相同名称不同的文件
  -k, --insecure        忽略SSL错误

Playlist optional options:
  --first FIRST         the first number
  --last LAST           the last number
  --size PAGE_SIZE, --page-size PAGE_SIZE
                        the page size number

Proxy options:
  -x HOST:PORT, --http-proxy HOST:PORT
                        Use an HTTP proxy for downloading
  -y HOST:PORT, --extractor-proxy HOST:PORT
                        Use an HTTP proxy for extracting only
  --no-proxy            Never use a proxy
  -s HOST:PORT or USERNAME:PASSWORD@HOST:PORT, --socks-proxy HOST:PORT or USERNAME:PASSWORD@HOST:PORT
                        Use an SOCKS5 proxy for downloading
D:\>

探测视频真实的播放地址:

抓取前可以设置的参数(不会实际去下载)
  -i, --info            打印提取的信息
  -u, --url             根据提供的url,打印提取的信息
  --json                以JSON格式打印提取的url

示例:探测视频真实的播放地址

  • you-get -u https://www.bilibili.com/video/BV1Xz4y127Yo
  • you-get --json https://www.bilibili.com/video/BV1Xz4y127Yo

wget 库

相关库:https://pypi.org/search/?q=wget

使用 wget 命令:​wget http://www.robots.ox.ac.uk/~ankush/data.tar.gz

python 调用 wget 命令实现下载

使用 python 的 wget 模块:pip install wget

import wget
import tempfile

url = 'https://p0.ifengimg.com/2019_30/1106F5849B0A2A2A03AAD4B14374596C76B2BDAB_w1000_h626.jpg'

# 获取文件名
file_name = wget.filename_from_url(url)
print(file_name)  #1106F5849B0A2A2A03AAD4B14374596C76B2BDAB_w1000_h626.jpg

# 下载文件,使用默认文件名,结果返回文件名
file_name = wget.download(url)
print(file_name) #1106F5849B0A2A2A03AAD4B14374596C76B2BDAB_w1000_h626.jpg

# 下载文件,重新命名输出文件名
target_name = 't1.jpg'
file_name = wget.download(url, out=target_name)
print(file_name) #t1.jpg

# 创建临时文件夹,下载到临时文件夹里
tmpdir = tempfile.gettempdir()
target_name = 't2.jpg'
file_name = wget.download(url, out=os.path.join(tmpdir, target_name))
print(file_name)  #/tmp/t2.jpg

ffmpeg

ffmpeg -ss 00:00:00 -i "https://vd4.bdstatic.com/mda-na67uu3bf6v85cnm/sc/cae_h264/1641533845968105062/mda-na67uu3bf6v85cnm.mp4?v_from_s=hkapp-haokan-hbe&auth_key=1641555906-0-0-642c8f9b47d4c37cc64d307be88df29d&bcevod_channel=searchbox_feed&pd=1&pt=3&logid=0906397151&vid=8050108300345362998&abtest=17376_2&klogid=0906397151" -t 00:05:00 -c copy "test.mp4"

ffmpeg 如何设置 header 信息

ffmpeg -user_agent "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36" -headers "sec-ch-ua: 'Chromium';v='88', 'Google Chrome';v='88', ';Not A Brand';v='99'"$'\r\n'"sec-ch-ua-mobile: ?0"$"Upgrade-Insecure-Requests: 1"  -i http://127.0.0.1:3000

如果只需要 ua 只加上 -user_agent 就可以。如果需要设置 -headers 其他选项时,多个选项用 $'\r\n' 链接起来。服务端接收数据格式正常,如图

ffmpeg 设置 header 请求头 UA 文件最大大小

ffmpeg -headers $'Origin: https://xxx.com\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36\r\nReferer: https://xxx.com' -threads 0 -i '地址' -c copy -y -f mpegts '文件名.ts' -v trace

使用-headers $’头一\r\n头二’添加header
注意顺序 ,放在命令行最后面无法生效!!!!!
后来输出了一下信息才发现问题
-v trace 用于输出当前的header信息方便调试
设置 UA 可以使用单独的 -user-agent 指令
在输出文件名前使用 -fs 1024K 限制为 1024K

ffmpeg 帮助:ffmpeg --help

Getting help:
    -h      -- print basic options
    -h long -- print more options
    -h full -- print all options (including all format and codec specific options, very long)
    -h type=name -- print all options for the named decoder/encoder/demuxer/muxer/filter/bsf/protocol
    See man ffmpeg for detailed description of the options.

Print help / information / capabilities:
-L                  show license
-h topic            show help
-? topic            show help
-help topic         show help
--help topic        show help
-version            show version
-buildconf          show build configuration
-formats            show available formats
-muxers             show available muxers
-demuxers           show available demuxers
-devices            show available devices
-codecs             show available codecs
-decoders           show available decoders
-encoders           show available encoders
-bsfs               show available bit stream filters
-protocols          show available protocols
-filters            show available filters
-pix_fmts           show available pixel formats
-layouts            show standard channel layouts
-sample_fmts        show available audio sample formats
-dispositions       show available stream dispositions
-colors             show available color names
-sources device     list sources of the input device
-sinks device       list sinks of the output device
-hwaccels           show available HW acceleration methods

Global options (affect whole program instead of just one file):
-loglevel loglevel  set logging level
-v loglevel         set logging level
-report             generate a report
-max_alloc bytes    set maximum size of a single allocated block
-y                  overwrite output files
-n                  never overwrite output files
-ignore_unknown     Ignore unknown stream types
-filter_threads     number of non-complex filter threads
-filter_complex_threads  number of threads for -filter_complex
-stats              print progress report during encoding
-max_error_rate maximum error rate  ratio of decoding errors (0.0: no errors, 1.0: 100% errors) above which ffmpeg returns an error instead of success.
-vol volume         change audio volume (256=normal)

Per-file main options:
-f fmt              force format
-c codec            codec name
-codec codec        codec name
-pre preset         preset name
-map_metadata outfile[,metadata]:infile[,metadata]  set metadata information of outfile from infile
-t duration         record or transcode "duration" seconds of audio/video
-to time_stop       record or transcode stop time
-fs limit_size      set the limit file size in bytes
-ss time_off        set the start time offset
-sseof time_off     set the start time offset relative to EOF
-seek_timestamp     enable/disable seeking by timestamp with -ss
-timestamp time     set the recording timestamp ('now' to set the current time)
-metadata string=string  add metadata
-program title=string:st=number...  add program with specified streams
-target type        specify target file type ("vcd", "svcd", "dvd", "dv" or "dv50" with optional prefixes "pal-", "ntsc-" or "film-")
-apad               audio pad
-frames number      set the number of frames to output
-filter filter_graph  set stream filtergraph
-filter_script filename  read stream filtergraph description from a file
-reinit_filter      reinit filtergraph on input parameter changes
-discard            discard
-disposition        disposition

Video options:
-vframes number     set the number of video frames to output
-r rate             set frame rate (Hz value, fraction or abbreviation)
-fpsmax rate        set max frame rate (Hz value, fraction or abbreviation)
-s size             set frame size (WxH or abbreviation)
-aspect aspect      set aspect ratio (4:3, 16:9 or 1.3333, 1.7777)
-vn                 disable video
-vcodec codec       force video codec ('copy' to copy stream)
-timecode hh:mm:ss[:;.]ff  set initial TimeCode value.
-pass n             select the pass number (1 to 3)
-vf filter_graph    set video filters
-ab bitrate         audio bitrate (please use -b:a)
-b bitrate          video bitrate (please use -b:v)
-dn                 disable data

Audio options:
-aframes number     set the number of audio frames to output
-aq quality         set audio quality (codec-specific)
-ar rate            set audio sampling rate (in Hz)
-ac channels        set number of audio channels
-an                 disable audio
-acodec codec       force audio codec ('copy' to copy stream)
-vol volume         change audio volume (256=normal)
-af filter_graph    set audio filters

Subtitle options:
-s size             set frame size (WxH or abbreviation)
-sn                 disable subtitle
-scodec codec       force subtitle codec ('copy' to copy stream)
-stag fourcc/tag    force subtitle tag/fourcc
-fix_sub_duration   fix subtitles duration
-canvas_size size   set canvas size (WxH or abbreviation)
-spre preset        set the subtitle options to the indicated preset

多线程 下载 文件

示例代码:

# 在python3下测试
 
import sys
import requests
import threading
import datetime
 
# 传入的命令行参数,要下载文件的url
url = sys.argv[1]
 
 
def Handler(start, end, url, filename):
    
    headers = {'Range': 'bytes=%d-%d' % (start, end)}
    r = requests.get(url, headers=headers, stream=True)
    
    # 写入文件对应位置
    with open(filename, "r+b") as fp:
        fp.seek(start)
        var = fp.tell()
        fp.write(r.content)
 
 
def download_file(url, num_thread = 5):
    
    r = requests.head(url)
    try:
        file_name = url.split('/')[-1]
        file_size = int(r.headers['content-length'])   # Content-Length获得文件主体的大小,当http服务器使用Connection:keep-alive时,不支持Content-Length
    except:
        print("检查URL,或不支持对线程下载")
        return
 
    #  创建一个和要下载文件一样大小的文件
    fp = open(file_name, "wb")
    fp.truncate(file_size)
    fp.close()
 
    # 启动多线程写文件
    part = file_size // num_thread  # 如果不能整除,最后一块应该多几个字节
    for i in range(num_thread):
        start = part * i
        if i == num_thread - 1:   # 最后一块
            end = file_size
        else:
            end = start + part
 
        t = threading.Thread(target=Handler, kwargs={'start': start, 'end': end, 'url': url, 'filename': file_name})
        t.setDaemon(True)
        t.start()
 
    # 等待所有线程下载完成
    main_thread = threading.current_thread()
    for t in threading.enumerate():
        if t is main_thread:
            continue
        t.join()
    print('%s 下载完成' % file_name)
 
if __name__ == '__main__':
    start = datetime.datetime.now().replace(microsecond=0)  
    download_file(url)
    end = datetime.datetime.now().replace(microsecond=0)
    print("用时: ", end='')
    print(end-start)

下载 "图片、音乐、视频"

# -*- coding:utf-8 -*-

import re
import requests
from contextlib import closing
from lxml import etree


class Spider(object):
    """ crawl image """
    def __init__(self):
        self.index = 0
        self.url = "http://www.xiaohuar.com"
        self.proxies = {"http": "http://172.17.18.80:8080", "https": "https://172.17.18.80:8080"}
        pass

    def download_image(self, image_url):
        real_url = self.url + image_url
        print "downloading the {0} image".format(self.index)
        with open("{0}.jpg".format(self.index), 'wb') as f:
            self.index += 1
            f.write(requests.get(real_url, proxies=self.proxies).content)
            pass
        pass

    def start_crawl(self):
        start_url = "http://www.xiaohuar.com/hua/"
        r = requests.get(start_url, proxies=self.proxies)
        if r.status_code == 200:
            temp = r.content.decode("gbk")
            html = etree.HTML(temp)
            links = html.xpath('//div[@class="item_t"]//img/@src')
            map(self.download_image, links)
            # next_page_url = html.xpath('//div[@class="page_num"]//a/text()')
            # print next_page_url[-1]
            # print next_page_url[-2]
            # print next_page_url[-3]
            next_page_url = html.xpath(u'//div[@class="page_num"]//a[contains(text(),"下一页")]/@href')
            page_num = 2
            while next_page_url:
                print "download {0} page images".format(page_num)
                r_next = requests.get(next_page_url[0], proxies=self.proxies)
                if r_next.status_code == 200:
                    html = etree.HTML(r_next.content.decode("gbk"))
                    links = html.xpath('//div[@class="item_t"]//img/@src')
                    map(self.download_image, links)
                    try:
                        next_page_url = html.xpath(u'//div[@class="page_num"]//a[contains(text(),"下一页")]/@href')
                    except BaseException as e:
                        next_page_url = None
                        print e
                    page_num += 1
                    pass
                else:
                    print "response status code : {0}".format(r_next.status_code)
                pass
        else:
            print "response status code : {0}".format(r.status_code)
        pass


class ProgressBar(object):
    def __init__(self, title, count=0.0, run_status=None, fin_status=None, total=100.0, unit='', sep='/', chunk_size=1.0):
        super(ProgressBar, self).__init__()
        self.info = "[%s] %s %.2f %s %s %.2f %s"
        self.title = title
        self.total = total
        self.count = count
        self.chunk_size = chunk_size
        self.status = run_status or ""
        self.fin_status = fin_status or " " * len(self.status)
        self.unit = unit
        self.seq = sep

    def __get_info(self):
        # 【名称】状态 进度 单位 分割线 总数 单位
        _info = self.info % (self.title, self.status,
                             self.count / self.chunk_size, self.unit, self.seq, self.total / self.chunk_size, self.unit)
        return _info

    def refresh(self, count=1, status=None):
        self.count += count
        # if status is not None:
        self.status = status or self.status
        end_str = "\r"
        if self.count >= self.total:
            end_str = '\n'
            self.status = status or self.fin_status
        print self.__get_info(), end_str


def download_mp4(video_url):
    print video_url
    try:
        with closing(requests.get(video_url.strip().decode(), stream=True)) as response:
            chunk_size = 1024
            with open('./{0}'.format(video_url.split('/')[-1]), "wb") as f:
                for data in response.iter_content(chunk_size=chunk_size):
                    f.write(data)
                    f.flush()

    except BaseException as e:
        print e
        return


def mp4():
    proxies = {"http": "http://172.17.18.80:8080", "https": "https://172.17.18.80:8080"}
    url = "http://www.budejie.com/video/"
    r = requests.get(url)
    print r.url
    if r.status_code == 200:
        print "status_code:{0}".format(r.status_code)
        content = r.content
        video_urls_compile = re.compile("http://.*?\.mp4")
        video_urls = re.findall(video_urls_compile, content)
        print len(video_urls)
        # print video_urls
        map(download_mp4, video_urls)
    else:
        print "status_code:{0}".format(r.status_code)


def mp3():
    proxies = {"http": "http://172.17.18.80:8080", "https": "https://172.17.18.80:8080"}
    with closing(requests.get("http://www.futurecrew.com/skaven/song_files/mp3/razorback.mp3", proxies=proxies, stream=True)) as response:
        chunk_size = 1024
        content_size = int(response.headers['content-length'])
        progress = ProgressBar("razorback", total=content_size, unit="KB", chunk_size=chunk_size, run_status="正在下载",
                               fin_status="下载完成")
        # chunk_size = chunk_size < content_size and chunk_size or content_size
        with open('./file.mp3', "wb") as f:
            for data in response.iter_content(chunk_size=chunk_size):
                f.write(data)
                progress.refresh(count=len(data))


if __name__ == "__main__":
    t = Spider()
    t.start_crawl()   
    mp3()
    mp4()   
    pass

下载视频的效果

另一个下载图片示例代码:

( github 地址:https://github.com/injetlee/Python/blob/master/爬虫集合/meizitu.py )

包括了创建文件夹,利用多线程爬取,设置的是5个线程,可以根据自己机器自己来设置一下。

import requests
import os
import time
import threading
from bs4 import BeautifulSoup


def download_page(url):
   '''
   用于下载页面
   '''
   headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0"}
   r = requests.get(url, headers=headers)
   r.encoding = 'gb2312'
   return r.text


def get_pic_list(html):
   '''
   获取每个页面的套图列表,之后循环调用get_pic函数获取图片
   '''
   soup = BeautifulSoup(html, 'html.parser')
   pic_list = soup.find_all('li', class_='wp-item')
   for i in pic_list:
       a_tag = i.find('h3', class_='tit').find('a')
       link = a_tag.get('href')
       text = a_tag.get_text()
       get_pic(link, text)


def get_pic(link, text):
   '''
   获取当前页面的图片,并保存
   '''
   html = download_page(link)  # 下载界面
   soup = BeautifulSoup(html, 'html.parser')
   pic_list = soup.find('div', id="picture").find_all('img')  # 找到界面所有图片
   headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0"}
   create_dir('pic/{}'.format(text))
   for i in pic_list:
       pic_link = i.get('src')  # 拿到图片的具体 url
       r = requests.get(pic_link, headers=headers)  # 下载图片,之后保存到文件
       with open('pic/{}/{}'.format(text, link.split('/')[-1]), 'wb') as f:
           f.write(r.content)
           time.sleep(1)   # 休息一下,不要给网站太大压力,避免被封


def create_dir(name):
   if not os.path.exists(name):
       os.makedirs(name)


def execute(url):
   page_html = download_page(url)
   get_pic_list(page_html)


def main():
   create_dir('pic')
   queue = [i for i in range(1, 72)]   # 构造 url 链接 页码。
   threads = []
   while len(queue) > 0:
       for thread in threads:
           if not thread.is_alive():
               threads.remove(thread)
       while len(threads) < 5 and len(queue) > 0:   # 最大线程数设置为 5
           cur_page = queue.pop(0)
           url = 'http://meizitu.com/a/more_{}.html'.format(cur_page)
           thread = threading.Thread(target=execute, args=(url,))
           thread.setDaemon(True)
           thread.start()
           print('{}正在下载{}页'.format(threading.current_thread().name, cur_page))
           threads.append(thread)


if __name__ == '__main__':
   main()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值