python爬虫实现POST request payload形式的请求


Posted in Python onApril 30, 2020

1. 背景

最近在爬取某个站点时,发现在POST数据时,使用的数据格式是request payload,有别于之前常见的 POST数据格式(Form data)。而使用Form data数据的提交方式时,无法提交成功。

python爬虫实现POST request payload形式的请求

1.1. Http请求中Form Data 和 Request Payload的区别

AJAX Post请求中常用的两种传参数的形式:form data 和 request payload

1.1.1. Form data

get请求的时候,我们的参数直接反映在url里面,形式为key1=value1&key2=value2形式,比如:

http://news.baidu.com/ns?word=NBA&tn=news&from=news&cl=2&rn=20&ct=1

而如果是post请求,那么表单参数是在请求体中,也是以key1=value1&key2=value2的形式在请求体中。通过chrome的开发者工具可以看到,如下:

RequestURL:http://127.0.0.1:8080/test/test.do
Request Method:POST
Status Code:200 OK

Request Headers
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip,deflate,sdch
Accept-Language:zh-CN,zh;q=0.8,en;q=0.6
AlexaToolbar-ALX_NS_PH:AlexaToolbar/alxg-3.2
Cache-Control:max-age=0
Connection:keep-alive
Content-Length:25
Content-Type:application/x-www-form-urlencoded
Cookie:JSESSIONID=74AC93F9F572980B6FC10474CD8EDD8D
Host:127.0.0.1:8080
Origin:http://127.0.0.1:8080
Referer:http://127.0.0.1:8080/test/index.jsp
User-Agent:Mozilla/5.0 (Windows NT 6.1)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.149 Safari/537.36

Form Data
name:mikan
address:street

Response Headers
Content-Length:2
Date:Sun, 11 May 2014 11:05:33 GMT
Server:Apache-Coyote/1.1

这里要注意post请求的Content-Type为application/x-www-form-urlencoded(默认的),参数是在请求体中,即上面请求中的Form Data。

前端代码:提交数据

xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");

xhr.send("name=foo&value=bar");

后端代码:接收提交的数据。在servlet中,可以通过request.getParameter(name)的形式来获取表单参数。

/**
 * 获取httpRequest的参数
 * 
 * @param request
 * @param name
 * @return
 */
protected String getParameterValue(HttpServletRequest request, String name) {
 return StringUtils.trimToEmpty(request.getParameter(name));
}

1.1.2. Request payload

如果使用原生AJAX POST请求的话,那么请求在chrome的开发者工具的表现如下,主要是参数在

Remote Address:192.168.234.240:80
Request URL:http://tuanbeta3.XXX.com/qimage/upload.htm
Request Method:POST
Status Code:200 OK

Request Headers
Accept:application/json, text/javascript, */*; q=0.01
Accept-Encoding:gzip,deflate,sdch
Accept-Language:zh-CN,zh;q=0.8,en;q=0.6
Connection:keep-alive
Content-Length:151
Content-Type:application/json;charset=UTF-8
Cookie:JSESSIONID=E08388788943A651924CA0A10C7ACAD0
Host:tuanbeta3.XXX.com
Origin:http://tuanbeta3.XXX.com
Referer:http://tuanbeta3.XXX.com/qimage/customerlist.htm?menu=19
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36
X-Requested-With:XMLHttpRequest

Request Payload
[{widthEncode:NNNcaXN, heightEncode:NNNN5NN, displayUrl:201409/03/66I5P266rtT86oKq6,…}]

Response Headers
Connection:keep-alive
Content-Encoding:gzip
Content-Type:application/json;charset=UTF-8
Date:Thu, 04 Sep 2014 06:49:44 GMT
Server:nginx/1.4.7
Transfer-Encoding:chunked
Vary:Accept-Encoding

注意请求的Content-Type是application/json;charset=UTF-8,而请求表单的参数在Request Payload中。

后端代码:获取数据(这里使用org.apache.commons.io.):

/**
 * 从 request 获取 payload 数据
 *
 * @param request
 * @return
 * @throws IOException
 */
private String getRequestPayload(HttpServletRequest request) throws IOException {
 return IOUtils.toString(request.getReader());
}

1.1.3. 二者区别

如果一个请求的Content-Type设置为application/x-www-form-urlencoded,那么这个Post请求会被认为是Http Post表单请求,那么请求主体将以一个标准的键值对和&的querystring形式出现。这种方式是HTML表单的默认设置,所以在过去这种方式更加常见。

其他形式的POST请求,是放到 Request payload 中(现在是为了方便阅读,使用了Json这样的数据格式),请求的Content-Type设置为application/json;charset=UTF-8或者不指定。

2. 环境

python 3.6.1

系统:win7

IDE:pycharm

requests 2.14.2

scrapy 1.4.0

3. 使用requests模块post payload请求

import json
import requests
import datetime

postUrl = 'https://sellercentral.amazon.com/fba/profitabilitycalculator/getafnfee?profitcalcToken=en2kXFaY81m513NydhTZ9sdb6hoj3D'
# payloadData数据
payloadData = {
 'afnPriceStr': 10,
 'currency':'USD',
 'productInfoMapping': {
  'asin': 'B072JW3Z6L',
  'dimensionUnit': 'inches',
 }
}
# 请求头设置
payloadHeader = {
 'Host': 'sellercentral.amazon.com',
 'Content-Type': 'application/json',
}
# 下载超时
timeOut = 25
# 代理
proxy = "183.12.50.118:8080"
proxies = {
 "http": proxy,
 "https": proxy,
}
r = requests.post(postUrl, data=json.dumps(payloadData), headers=payloadHeader)
dumpJsonData = json.dumps(payloadData)
print(f"dumpJsonData = {dumpJsonData}")
res = requests.post(postUrl, data=dumpJsonData, headers=payloadHeader, timeout=timeOut, proxies=proxies, allow_redirects=True)
# 下面这种直接填充json参数的方式也OK
# res = requests.post(postUrl, json=payloadData, headers=header)
print(f"responseTime = {datetime.datetime.now()}, statusCode = {res.status_code}, res text = {res.text}")

4. 在scrapy中post payload请求

这儿有个坏消息,那就是scrapy目前还不支持payload这种request请求。而且scrapy对formdata的请求也有很严格的要求,具体可以参考这篇文章:https://3water.com/article/185824.htm

4.1. 分析scrapy源码

参考注解

# 文件:E:\Miniconda\Lib\site-packages\scrapy\http\request\form.py
class FormRequest(Request):

 def __init__(self, *args, **kwargs):
  formdata = kwargs.pop('formdata', None)
  if formdata and kwargs.get('method') is None:
   kwargs['method'] = 'POST'

  super(FormRequest, self).__init__(*args, **kwargs)

  if formdata:
   items = formdata.items() if isinstance(formdata, dict) else formdata
   querystr = _urlencode(items, self.encoding)
   # 这儿写死了,当提交数据时,设置好Content-Type,也就是form data类型
   # 就算改写这儿,后面也没有对 json数据解析的处理
   if self.method == 'POST':
    self.headers.setdefault(b'Content-Type', b'application/x-www-form-urlencoded')
    self._set_body(querystr)
   else:
    self._set_url(self.url + ('&' if '?' in self.url else '?') + querystr)

4.2. 思路:在scrapy中嵌入requests模块

分析请求

返回的查询结果

第一步:在爬虫中构造请求,把所有的参数以及必要信息带进去。

python爬虫实现POST request payload形式的请求

python爬虫实现POST request payload形式的请求

python爬虫实现POST request payload形式的请求

返回的查询结果

python爬虫实现POST request payload形式的请求

第一步:在爬虫中构造请求,把所有的参数以及必要信息带进去。

# 文件 mySpider.py中

payloadData = {}
payloadData['afnPriceStr'] = 0
payloadData['currency'] = asinInfo['currencyCodeHidden']
payloadData['futureFeeDate'] = asinInfo['futureFeeDateHidden']
payloadData['hasFutureFee'] = False
payloadData['hasTaxPage'] = True
payloadData['marketPlaceId'] = asinInfo['marketplaceIdHidden']
payloadData['mfnPriceStr'] = 0
payloadData['mfnShippingPriceStr'] = 0
payloadData['productInfoMapping'] = {}
payloadData['productInfoMapping']['asin'] = dataFieldJson['asin']
payloadData['productInfoMapping']['binding'] = dataFieldJson['binding']
payloadData['productInfoMapping']['dimensionUnit'] = dataFieldJson['dimensionUnit']
payloadData['productInfoMapping']['dimensionUnitString'] = dataFieldJson['dimensionUnitString']
payloadData['productInfoMapping']['encryptedMarketplaceId'] = dataFieldJson['encryptedMarketplaceId']
payloadData['productInfoMapping']['gl'] = dataFieldJson['gl']
payloadData['productInfoMapping']['height'] = dataFieldJson['height']
payloadData['productInfoMapping']['imageUrl'] = dataFieldJson['imageUrl']
payloadData['productInfoMapping']['isAsinLimits'] = dataFieldJson['isAsinLimits']
payloadData['productInfoMapping']['isWhiteGloveRequired'] = dataFieldJson['isWhiteGloveRequired']
payloadData['productInfoMapping']['length'] = dataFieldJson['length']
payloadData['productInfoMapping']['link'] = dataFieldJson['link']
payloadData['productInfoMapping']['originalUrl'] = dataFieldJson['originalUrl']
payloadData['productInfoMapping']['productGroup'] = dataFieldJson['productGroup']
payloadData['productInfoMapping']['subCategory'] = dataFieldJson['subCategory']
payloadData['productInfoMapping']['thumbStringUrl'] = dataFieldJson['thumbStringUrl']
payloadData['productInfoMapping']['title'] = dataFieldJson['title']
payloadData['productInfoMapping']['weight'] = dataFieldJson['weight']
payloadData['productInfoMapping']['weightUnit'] = dataFieldJson['weightUnit']
payloadData['productInfoMapping']['weightUnitString'] = dataFieldJson['weightUnitString']
payloadData['productInfoMapping']['width'] = dataFieldJson['width']
# https://sellercentral.amazon.com/fba/profitabilitycalculator/getafnfee?profitcalcToken=en2kXFaY81m513NydhTZ9sdb6hoj3D
postUrl = f"https://sellercentral.amazon.com/fba/profitabilitycalculator/getafnfee?profitcalcToken={asinInfo['tokenValue']}"
payloadHeader = {
 'Host': 'sellercentral.amazon.com',
 'Content-Type': 'application/json',
}
# scrapy源码:self.headers.setdefault(b'Content-Type', b'application/x-www-form-urlencoded')
print(f"payloadData = {payloadData}")
# 这个request并不真正用来调度,去发出请求,因为这种方式构造方式,是无法提交成功的,会返回404错误
# 这样构造主要是把查询参数提交出去,在下载中间件部分用request模块下载,用 “payloadFlag” 标记这种request
yield Request(url = postUrl,
    headers = payloadHeader,
    meta = {'payloadFlag': True, 'payloadData': payloadData, 'headers': payloadHeader, 'asinInfo': asinInfo},
    callback = self.parseAsinSearchFinallyRes,
    errback = self.error,
    dont_filter = True
    )

第二步:在中间件中,用requests模块处理这个请求

# 文件:middlewares.py

class PayLoadRequestMiddleware:
 def process_request(self, request, spider):
  # 如果有的请求是带有payload请求的,在这个里面处理掉
  if request.meta.get('payloadFlag', False):
   print(f"PayLoadRequestMiddleware enter")
   postUrl = request.url
   headers = request.meta.get('headers', {})
   payloadData = request.meta.get('payloadData', {})
   proxy = request.meta['proxy']
   proxies = {
    "http": proxy,
    "https": proxy,
   }
   timeOut = request.meta.get('download_timeout', 25)
   allow_redirects = request.meta.get('dont_redirect', False)
   dumpJsonData = json.dumps(payloadData)
   print(f"dumpJsonData = {dumpJsonData}")
   # 发现这个居然是个同步 阻塞的过程,太过影响速度了
   res = requests.post(postUrl, data=dumpJsonData, headers=headers, timeout=timeOut, proxies=proxies, allow_redirects=allow_redirects)
   # res = requests.post(postUrl, json=payloadData, headers=header)
   print(f"responseTime = {datetime.datetime.now()}, res text = {res.text}, statusCode = {res.status_code}")
   if res.status_code > 199 and res.status_code < 300:
    # 返回Response,就进入callback函数处理,不会再去下载这个请求
    return HtmlResponse(url=request.url,
         body=res.content,
         request=request,
         # 最好根据网页的具体编码而定
         encoding='utf-8',
         status=200)
   else:
    print(f"request mode getting page error, Exception = {e}")
    return HtmlResponse(url=request.url, status=500, request=request)

4.3. 遗留下的问题

scrapy之所以强大,就是因为并发度高。大家都知道,由于Python GIL的原因,导致python无法通过多线程来提高性能。但是至少可以做到下载与解析同步的过程,在下载空档的时候,进行数据的解析,调度等等,这都归功于scrapy采用的异步结构。

但是,我们在中间件中使用requests模块进行网页下载,因为这是个同步过程,所以会阻塞在这个地方,拉低了整个爬虫的效率。

所以,需要根据项目具体的情况,来决定合适的方案。当然这里又涉及到一个新的话题,就是scrapy提供的两种爬取模式:深度优先模式和广度优先模式。如何尽可能最大限度的利用scrapy的并发?在环境不稳定的情形下如何保证尽可能稳定的拿到数据?

深度优先模式和广度优先模式是在settings中设置的。

# 文件: settings.py

# DEPTH_PRIORITY(默认值为0)设置为一个正值后,Scrapy的调度器的队列就会从LIFO变成FIFO,因此抓取规则就由DFO(深度优先)变成了BFO(广度优先)
DEPTH_PRIORITY = 1, # 广度优先(肯呢个会累积大量的request,累计占有大量的内存,最终数据也在最后一批爬取)

深度优先:DEPTH_PRIORITY = 0

python爬虫实现POST request payload形式的请求

广度优先:DEPTH_PRIORITY = 1

python爬虫实现POST request payload形式的请求

想将这个过程做成异步的,一直没有思路,欢迎大神提出好的想法

以上这篇python爬虫实现POST request payload形式的请求就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
高性能web服务器框架Tornado简单实现restful接口及开发实例
Jul 16 Python
python3序列化与反序列化用法实例
May 26 Python
机器学习python实战之手写数字识别
Nov 01 Python
对python添加模块路径的三种方法总结
Oct 16 Python
用Pycharm实现鼠标滚轮控制字体大小的方法
Jan 15 Python
Python实现多进程的四种方式
Feb 22 Python
详解python运行三种方式
May 13 Python
PyTorch中Tensor的拼接与拆分的实现
Aug 18 Python
Python pandas.DataFrame 找出有空值的行
Sep 09 Python
Python post请求实现代码实例
Feb 28 Python
python归并排序算法过程实例讲解
Nov 04 Python
python3 googletrans超时报错问题及翻译工具优化方案 附源码
Dec 23 Python
Pycharm IDE的安装和使用教程详解
Apr 30 #Python
scrapy爬虫:scrapy.FormRequest中formdata参数详解
Apr 30 #Python
Python爬虫:Request Payload和Form Data的简单区别说明
Apr 30 #Python
如何配置关联Python 解释器 Anaconda的教程(图解)
Apr 30 #Python
python针对Oracle常见查询操作实例分析
Apr 30 #Python
python实现Oracle查询分组的方法示例
Apr 30 #Python
Pytorch数据拼接与拆分操作实现图解
Apr 30 #Python
You might like
php读取文件内容的方法汇总
2015/01/24 PHP
thinkPHP通用控制器实现方法示例
2017/11/23 PHP
laravel dingo API返回自定义错误信息的实例
2019/09/29 PHP
js 禁止选择功能实现代码(兼容IE/Firefox)
2010/04/23 Javascript
jQuery+jqmodal弹出窗口实现代码分明
2010/06/14 Javascript
元素的内联事件处理函数的特殊作用域在各浏览器中存在差异
2011/01/12 Javascript
浅析js中2个等号与3个等号的区别
2013/08/06 Javascript
js实现简单登录功能的实例代码
2013/11/09 Javascript
使用jQuery动态加载js脚本文件的方法
2014/04/03 Javascript
js 数组去重的四种实用方法
2014/09/09 Javascript
Windows下用PyCharm和Visual Studio开始Python编程
2015/10/26 Javascript
又一款js时钟!transform实现时钟效果
2016/08/15 Javascript
js html5 css俄罗斯方块游戏再现
2016/10/17 Javascript
Vue.JS入门教程之处理表单
2016/12/01 Javascript
js实现九宫格的随机颜色跳转
2017/02/19 Javascript
vue2.0中goods选购栏滚动算法的实现代码
2017/05/17 Javascript
浅谈mvvm-simple双向绑定简单实现
2018/04/18 Javascript
在Vue组件中获取全局的点击事件方法
2018/09/06 Javascript
使用url_helper简化Python中Django框架的url配置教程
2015/05/30 Python
python爬虫_自动获取seebug的poc实例
2017/08/05 Python
Python 操作文件的基本方法总结
2017/08/10 Python
Python绘制并保存指定大小图像的方法
2019/01/10 Python
详解Python网络框架Django和Scrapy安装指南
2019/04/01 Python
Django 使用easy_thumbnails压缩上传的图片方法
2019/07/26 Python
Python连接Impala实现步骤解析
2020/08/04 Python
Python从MySQL数据库中面抽取试题,生成试卷
2021/01/14 Python
html5基础标签(html5视频标签 html5新标签用法)
2013/12/30 HTML / CSS
KLOOK客路:发现更好玩的世界,预订独一无二的旅行体验
2016/12/16 全球购物
戴尔新加坡官网:Dell Singapore
2020/12/13 全球购物
介绍一下HDLC(High-Level Data Link Control)高层数据链路协议
2012/01/21 面试题
C#基础面试题
2016/10/17 面试题
乡党政领导班子群众路线教育实践活动个人对照检查材料
2014/09/20 职场文书
2014年英语教学工作总结
2014/12/17 职场文书
学校党员干部承诺书
2015/05/04 职场文书
同事离别感言
2015/08/04 职场文书
婚宴祝酒词大全
2015/08/10 职场文书