python 使用标头和参数请求 POST



我有一个 post 请求,我正在尝试使用 python 中的requests发送。但是我收到无效的 403 错误。请求通过浏览器正常工作。

POST /ajax-load-system HTTP/1.1
Host: xyz.website.com
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-GB,en;q=0.5
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0
Referer: http://xyz.website.com/help-me/ZYc5Yn
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 56
Cookie: csrf_cookie_name=a3f8adecbf11e29c006d9817be96e8d4; ci_session=ba92hlh6o0ns7f20t4bsgjt0uqfdmdtl; _ga=GA1.2.1535910352.1530452604; _gid=GA1.2.1416631165.1530452604; _gat_gtag_UA_21820217_30=1
Connection: close
csrf_test_name=a3f8adecbf11e29c006d9817be96e8d4&vID=9999

我在python中尝试的是:

import requests
import json
url = 'http://xyz.website.com/ajax-load-system'
payload = {
'Host': 'xyz.website.com',
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'en-GB,en;q=0.5',
'Referer': 'http://xyz.website.com/help-me/ZYc5Yn',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'Content-Length': '56',
'Cookie': 'csrf_cookie_name=a3f8adecbf11e29c006d9817be96e8d4; ci_session=ba92hlh6o0ns7f20t4bsgjt0uqfdmdtl; _ga=GA1.2.1535910352.1530452604; _gid=GA1.2.1416631165.1530452604; _gat_gtag_UA_21820217_30=1',
'Connection': 'close',
'csrf_test_name': 'a3f8adecbf11e29c006d9817be96e8d4',
'vID': '9999',
}    
headers = {}
r = requests.post(url, headers=headers, data=json.dumps(payload))
print(r.status_code)  

但这正在打印一个403错误代码。我在这里做错了什么?

我期待以 json 的形式返回响应:

{"status_message":"Thanks for help.","help_count":"141","status":true}

您混淆了标头和有效负载,有效负载不是 JSON 编码的。

这些都是标头:

Host: xyz.website.com
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-GB,en;q=0.5
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0
Referer: http://xyz.website.com/help-me/ZYc5Yn
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 56
Cookie: csrf_cookie_name=a3f8adecbf11e29c006d9817be96e8d4; ci_session=ba92hlh6o0ns7f20t4bsgjt0uqfdmdtl; _ga=GA1.2.1535910352.1530452604; _gid=GA1.2.1416631165.1530452604; _gat_gtag_UA_21820217_30=1
Connection: close

其中大多数是自动化的,不需要手动设置。requests会根据 URL 为您设置HostAccept设置为可接受的默认值,Accept-Language在这些情况下很少需要,Referer,除非使用 HTTPS,否则出于隐私原因通常甚至不会设置或过滤掉,因此网站不再依赖于它被设置,Content-Type必须实际反映您的POST的内容(并且不是 JSON! 因此,requests根据您的调用方式为您设置此内容,Content-Length必须反映实际的内容长度,因此由requests设置,因为它处于计算此值的最佳位置,并且Connection绝对应该由库处理,因为您不想阻止它有效地重用连接(如果可以的话(。

充其量你可以设置X-Requested-WithUser-Agent,但前提是服务器不会接受请求。Cookies标头反映了浏览器持有的 Cookie 的值。您的脚本可以使用请求会话对象从服务器获取自己的一组 cookie,以向Referer标头中命名的 url(或同一站点上的其他合适 URL(发出初始GET请求,此时服务器应在响应上设置 Cookie,这些 cookie 将存储在会话中以便在 post 请求中重复使用。使用该机制获取您自己的 CSRF cookie 值。

请注意Content-Type标头:

Content-Type: application/x-www-form-urlencoded; charset=UTF-8

当您将字典传递给requests.post()函数的data关键字时,库会将数据编码为完全适合您的内容类型。

实际有效载荷为

csrf_test_name=a3f8adecbf11e29c006d9817be96e8d4&vID=9999

这是两个字段,csrf_test_namevID,需要成为payload字典的一部分。

请注意,csrf_test_name与 Cookie 中的csrf_cookie_name值匹配。这就是网站保护自己免受跨站伪造攻击的方式,第三方可能会尝试代表您发布到同一 URL。此类第三方将无法访问相同的 cookie,因此将被阻止。您的代码需要获取新的 cookie;正确的 CSRF 实现将限制任何 CSRF cookie 可以重复使用的时间。

因此,至少需要使这一切正常工作的是:

# *optional*, the site may not care about these. If they *do* care, then
# they care about keeping out automated scripts and could in future 
# raise the stakes and require more 'browser-like' markers. Ask yourself
# if you want to anger the site owners and get into an arms race.
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0',
'X-Requested-With': 'XMLHttpRequest',
}
payload = {
'vID': 9999,
}
url = 'http://xyz.website.com/ajax-load-system'
# the URL from the Referer header, but others at the site would probably
# also work
initial_url = 'http://xyz.website.com/help-me/ZYc5Yn'
with requests.Session() as session:
# obtain CSRF cookie
initial_response  = session.get(initial_url)
payload['csrf_test_name'] = session.cookies['csrf_cookie_name']
# Now actually post with the correct CSRF cookie
response = session.post(url, headers=headers, data=payload)

如果这仍然导致问题,则需要尝试另外两个标头,AcceptAccept-Language。考虑到这将意味着该网站已经对如何阻止自动网站抓取工具进行了长时间的思考。考虑与他们联系并询问他们是否提供 API 选项。