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 相关文章推荐
使用python实现拉钩网上的FizzBuzzWhizz问题示例
May 05 Python
Python 40行代码实现人脸识别功能
Apr 02 Python
Python3学习笔记之列表方法示例详解
Oct 06 Python
python版本的仿windows计划任务工具
Apr 30 Python
Pandas过滤dataframe中包含特定字符串的数据方法
Nov 07 Python
python多线程并发实例及其优化
Jun 27 Python
pytorch多进程加速及代码优化方法
Aug 19 Python
Django实现文件上传下载功能
Oct 06 Python
python如何将两张图片生成为全景图片
Mar 05 Python
基于Python制作一副扑克牌过程详解
Oct 19 Python
运行Python编写的程序方法实例
Oct 21 Python
Python生成pdf目录书签的实例方法
Oct 29 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 5.0对象模型深度探索之绑定
2006/09/05 PHP
PHP日期时间函数的高级应用技巧
2009/05/16 PHP
php将金额数字转化为中文大写
2015/07/09 PHP
php获取远程文件大小
2015/10/20 PHP
php实现连接access数据库并转txt写入的方法
2017/02/08 PHP
用倒置滤镜把div倒置,再把table倒置。
2007/07/31 Javascript
Jquery.TreeView结合ASP.Net和数据库生成菜单导航条
2010/08/27 Javascript
理解Javascript_07_理解instanceof实现原理
2010/10/15 Javascript
jquery常用操作小结
2014/07/21 Javascript
小米公司JavaScript面试题
2014/12/29 Javascript
jQuery 实时保存页面动态添加的数据的示例
2017/08/14 jQuery
Vue如何实现响应式系统
2018/07/11 Javascript
mpvue全局引入sass文件的方法步骤
2019/03/06 Javascript
浅谈vue中组件绑定事件时是否加.native
2019/11/09 Javascript
vue中移动端调取本地的复制的文本方式
2020/07/18 Javascript
通过实例解析json与jsonp原理及使用方法
2020/09/27 Javascript
JS常用跨域方法实现原理解析
2020/12/09 Javascript
Python时区设置方法与pytz查询时区教程
2013/11/27 Python
简单使用Python自动生成文章
2014/12/25 Python
介绍Python的Django框架中的QuerySets
2015/04/20 Python
Django的session中对于用户验证的支持
2015/07/23 Python
Python 基于Twisted框架的文件夹网络传输源码
2016/08/28 Python
python微信跳一跳系列之棋子定位像素遍历
2018/02/26 Python
python+pandas+时间、日期以及时间序列处理方法
2018/07/10 Python
Python3爬虫全国地址信息
2019/01/05 Python
python 实现手机自动拨打电话的方法(通话压力测试)
2019/08/08 Python
Anaconda的安装与虚拟环境建立
2020/11/18 Python
类成员函数的重载、覆盖和隐藏区别
2016/01/27 面试题
新品发布会策划方案
2014/06/08 职场文书
2014银行领导班子群众路线对照检查材料思想汇报
2014/09/17 职场文书
初婚未育证明样本
2014/10/24 职场文书
打架检讨书
2015/01/27 职场文书
租车协议书
2015/01/27 职场文书
男方婚前保证书
2015/02/28 职场文书
蓝天保卫战收官在即 :15行业将开展环保分级评价
2019/07/19 职场文书
2019新学期家长会工作计划
2019/08/21 职场文书