python - Python 请求在这里做错了什么,还是我的 POST 请求缺少某些东西?

标签 python post http-post tumblr python-requests

我目前正在编写一个程序,它将帮助用户确定在 tumblr 上发帖的最佳时间。与 Twitter 一样,大多数关注者都有如此多的订阅,以至于他们无法跟上,这意味着最好知道自己的特定关注者(大部分)何时在线。在 tumblr 上,这可以通过两种方式确定 - 首先他们最近是否分享了最近发布的任何内容,其次他们最近是否添加到了喜欢的帖子列表中。

令人沮丧的是,即使设置为“公共(public)”,任意用户(除了自己)的喜欢的帖子流也仅对登录的实体可用。据我所知,这意味着我要么必须经常将登录 cookie 上传到应用程序,要么让此后请求正常工作。

我已经通过 Opera 的检查器查看了许多成功的出站请求,但我一定仍然遗漏了一些东西,或者也许请求正在执行服务器拒绝的操作,无论我做什么。

问题的本质如下。目前这是用 Python 2.7 编写的 并使用Python requestsBeautifulSoup 。要自己运行它,请将 get_login_response() 顶部的 e 和 p 对更新为一组真实值。

import requests
from bs4 import BeautifulSoup

class Login:

    def __init__(self):
        self.session = requests.session()

    def get_hidden_fields(self):
        """ -> string. tumblr dynamically generates a key for its login forms
        This should extract that key from the form so that the POST-data to
        login will be accepted.
        """
        pageRequest = requests.Request("GET","https://www.tumblr.com/login")
        received = self.session.send( pageRequest.prepare() )
        html = BeautifulSoup(received.content)
        hiddenFieldDict = {}
        hiddenFields = html.find_all("input",type="hidden")
        for x in hiddenFields: hiddenFieldDict[x["name"]]=x["value"]
        return hiddenFieldDict

    def get_login_response(self):
        e = u"<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="bbdfdedadffbd7d2cdde95d8d4d6" rel="noreferrer noopener nofollow">[email protected]</a>"
        p = u"password"
        endpoint = u"https://tumblr.com/login"
        payload = { u"user[email]": e,
                    u"user[password]": p,
                    u"user[age]":u"",
                    u"tumblelog[name]": u"",
                    u"host": u"www.tumblr.com",
                    u"Connection:":u"keep-alive",
                    u"Context":u"login",
                    u"recaptcha_response_field":u""
                  }
        payload.update( self.get_hidden_fields() )
    ##        headers = {"Content-Type":"multipart/form-data"}
        headers = {u"Content-Type":u"application/x-www-form-urlencoded",
                   u"Connection:":u"keep-alive",
                   u"Origin":u"https://tumblr.com",
                   u"Referer": u"https://www.tumblr.com/login",
                   u"User-Agent":u"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36 OPR/18.0.1284.68",
                   u"Accept":u"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
                   u"Accept-Encoding":u"gzip,deflate,sdch",
                   u"Accept-Language":u"en-US,en;q=0.8",
                   u"Cache-Control":u"max-age=0"
                   #"Content-Length":VALUE is still needed
                   }
        # this cookie is stale but it seems we these for free anyways,
        #  so I'm not sure whether it's actually needed. It's mostly
        #  google analytics info.
        sendCookie = {"tmgioct":"52c720e28536530580783210",
                      "__qca":"P0-1402443420-1388781796773",
                      "pfs":"POIPdNt2p1qmlMGRbZH5JXo5k",
                      "last_toast":"1388783309",
                      "capture":"GDTLiEN5hEbMxPzys1ye1Gf4MVM",
                      "logged_in":"0",
                      "_ga":"GA1.2.2064992906.1388781797",
                      "devicePixelRatio":"1",
                      "documentWidth":"1280",
                      "anon_id":"VNHOJWQXGTQXHNCFKYJQUMUIVQBRISPR",
                      "__utma":"189990958.2064992906.1388781797.1388781797.1388781797.1",
                      "__utmb":"189990958.28.10.1388781797",
                      "__utmc":"189990958",
                      "__utmz":"189990958.1388781797.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)"}
        loginRequest = requests.Request("POST",
                                        endpoint,
                                        headers,
                                        data=payload,
                                        cookies=sendCookie # needed?
##                                        ,auth=(e,p) # may not be needed
                                        )

        contentLength = len(loginRequest.prepare().body)
        loginRequest.data.update({u"Content-Length":unicode(contentLength)})
        return self.session.send( loginRequest.prepare() )

l = Login()
res = l.get_login_response()
print "All cookies: ({})".format(len(l.session.cookies))
print l.session.cookies # has a single generic cookie from the initial GET query
print "Celebrate if non-empty:"
print res.cookies # this should theoretically contain the login cookie

我这边的输出:

All cookies: (1)
<<class 'requests.cookies.RequestsCookieJar'>[<Cookie tmgioct=52c773ed65cfa30622446430 for www.tumblr.com/>]>
Celebrate if non-empty:
<<class 'requests.cookies.RequestsCookieJar'>[]>

如果我的代码不安全,并且您还为我提供了有关该问题的指示,则可以获得奖励积分。我选择 requests 模块是因为它简单,但如果它缺乏功能并且我的目标可以使用 httplib2 或我愿意切换的东西。

最佳答案

有很多事情你没有做但你需要做,还有很多事情你正在做但你没有做。

首先,返回并检查登录请求中发送的 POST 字段。当我在 Chrome 中执行此操作时,我看到以下内容:

user[email]:<redacted>
user[password]:<redacted>
tumblelog[name]:
user[age]:
recaptcha_public_key:6Lf4osISAAAAAJHn-CxSkM9YFNbirusAOEmxqMlZ
recaptcha_response_field:
context:other
version:STANDARD
follow:
http_referer:http://www.tumblr.com/logout
form_key:!1231388831237|jS7l2SHeUMogRjxRiCbaJNVduXU
seen_suggestion:0
used_suggestion:0

您的基于请求的 POST 缺少其中一些字段,特别是 recaptcha_public_key , version , follow , http_referer , form_key , seen_suggestionused_suggestion .

这些字段不是可选的:它们需要在此 POST 上发送。其中一些可以安全地通用使用,但获取这些的最安全方法是获取登录页面本身的数据,并使用 BeautifulSoup 从 HTML 中提取值。我假设您具备执行此操作的技能(例如,您知道如何在 HTML 中查找表单输入并解析它们以获得默认值)。

这里的一个好习惯是开始使用 Wireshark 或 tcpdump 等工具来检查您的请求 HTTP 流量,并将其与从 Chrome/Opera 获得的流量进行比较。这将使您能够查看正在发送和未发送的内容,以及这两个请求有何不同。

其次,一旦您开始点击登录页面,您就不需要在 POST 上发送 Cookie,因此您可以停止这样做。更一般地,当使用请求 Session 时对象,您不应该输入任何额外的 cookie:只需模拟来自实际浏览器的 HTTP 请求流,您的 cookie 状态就可以了。

第三,您过度指定了 header 字典。您提供的大部分字段将由请求自动填充。现在,考虑到您正在尝试模拟浏览器(从外观上看是 Opera),您将需要覆盖其中的一些浏览器,但大多数都可以保留。您应该使用此 header 字典:

{
    u"Origin":u"https://tumblr.com",
    u"Referer": u"https://www.tumblr.com/login",
    u"User-Agent":u"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36 OPR/18.0.1284.68",
    u"Accept":u"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
    u"Accept-Language":u"en-US,en;q=0.8",
}

下面是我从您的 header 字典中删除的字段列表以及删除它们的原因:

  • 内容类型:当您向 data 提供字典时Requests 中的参数,我们将 Content-Type 设置为 application/x-www-form-urlencoded为你。没必要自己做。
  • 连接:Requests 管理 HTTP 连接池并自行保持事件状态:不要参与该过程,否则只会出错。
  • Accept-Encoding:再次强调,请让 Requests 设置此项,除非您确实准备好处理内容解码。请求只知道怎么做gzipdeflate :如果您发送sdch而实际上要拿回来,你必须自己解码。最好不要宣传你支持它。
  • Cache-Control:POST 请求无法缓存,因此无关紧要。

第四,我想在这里非常明确地指出,不要自己计算 Content-Length。请求会为你做这件事并且会做对。如果您自己发送该 header ,则可能会出现请求核心开发团队必须追赶的各种奇怪的错误。永远没有充分的理由自己设置该 header 。考虑到这一点,您可以停止使用 PreparedRequest对象并返回使用 session.post() .

关于python - Python 请求在这里做错了什么,还是我的 POST 请求缺少某些东西?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20916338/

相关文章:

android - HTTP POST 响应到 android 中的 WebView

java - 在 php 页面上执行来自 android 应用程序的表单发布

python - 在 Python 中查找打印语句

java - 上传图片的更好方法是什么

python - 在 python 中使用 requests 发布到 html 表单?

java - 如何在 Spring REST Controller 方法中从 RequestBody 获取参数

python - 替代 Python 中的二维数组

Python - 同一目录中的文件的 IOError : [Errno 2] No such file or directory: u'lastid. py'。在本地工作,不在 Heroku 上

python - 将 Z 值(Z 值,标准分数)转换为 Python 中正态分布的 p 值

Java - 如何为 GET 和 POST 打开一个 HttpURLConnection