Чрезвычайно странная проблема веб-скрепок: почтовый запрос не ведет себя так, как ожидалось

Я пытаюсь программно отправить некоторые данные в форму на нашей странице администрирования нашей компании, а не делать это вручную.

Я написал множество других инструментов, которые очищают этот сайт и манипулируют данными. Однако по какой-то причине этот конкретный вопрос дает мне тонну проблемы.

Прогулка через браузер:

Ниже приведены страницы, которые я пытаюсь очистить и отправить данные. Обратите внимание, что эти страницы обычно отображаются в js shadowboxes, однако он отлично работает с отключенным Javascript, поэтому я предполагаю, что javascript не является проблемой в отношении проблемы скребка.

(Обратите внимание: поскольку это страница компании, я заполнил, я заменил все поля формы на нежелательные заголовки, так что, например, номера клиентов полностью созданы)

Кроме того, поскольку это страница компании за стеной имени пользователя и пароля, я не могу выдавать веб-сайт для тестирования, поэтому я попытался внести как можно больше подробностей в этот пост!

Главная точка входа находится здесь:

enter image description here

На этой странице я нажимаю "Add New form", который открывает эту следующую страницу в новом теге (поскольку javascript отключен).

enter image description here

На этой странице я заполняю небольшую форму, нажмите кнопку "Отправить", после чего на следующей странице отображается сообщение об успешном завершении.

enter image description here

Должно быть просто, не так ли?

Попытка кода 1: Механизировать

import mechanize
import base64
import cookielib


br = mechanize.Browser()

username = 'USERNAME'
password = 'PASSWORD'
br.addheaders.append(('Authorization', 
    'Basic %s' % base64.encodestring('%s:%s' % (username, password))))
br.addheaders = [('User-agent', 
    'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.22 (KHTML,'
    ' like Gecko) Chrome/25.0.1364.172 Safari/537.22')]

br.open('www.our_company_page.com/adm/add_forms.php')

links = [link for link in br.links()]

# Follow "Add a form" Link
response = br.follow_link(links[0]) 

br.select_form(nr=0)
br.form.set_all_readonly(False)
br.form['formNumber'] = "FROM_PYTHON"
br.form['RevisionNumber'] = ['20']
br.form['FormType'] = ['H(num)']

response = br.submit()

print response.read() #Shows the exact same page! >:(

Итак, как вы можете видеть, я пытаюсь дублировать действия, которые я предпринимал в браузере. Загрузите начальную страницу /adm/forms, следуйте по первой ссылке, которая Add a Form, и заполните форму и нажмите кнопку submit. Но здесь, где он становится вялым. Ответ, который возвращает механизация, - это та же самая страница с формой. Нет сообщений об ошибках, сообщений об ошибках и когда я вручную проверяю нашу страницу администратора, никаких изменений не было сделано.

Проверка сетевой активности

Разочарованный, я открыл Chrome и наблюдал за вкладкой в ​​сети, когда я вручную подал заявку и отправил форму в браузере.

При отправке формы это сетевая активность:

enter image description here

Похоже на меня. Там post, а затем a get для css файлов и еще один get для библиотеки jquery. Там есть еще get для какого-то изображения, но я понятия не имею, для чего это.

Проверка деталей запроса POST:

enter image description here

После некоторого Googling о проблемах с выскабливанием, я увидел предположение, что сервер может ожидать определенный заголовок, и я должен просто скопировать все, что делается в запросе POST, а затем медленно отнять заголовки, пока не выясню, какой из них был важным. Поэтому я сделал именно это, скопировал каждый бит информации на вкладке "Сеть" и застрял в моем почтовом запросе.

Попытка кода 2: Urllib

У меня были некоторые проблемы с выяснением всего заголовка с Mechanize, поэтому я переключился на urllib2.

import urllib
import urllib2
import base64 



url = 'www.our_company_page.com/adm/add_forms.php'
values = {
    'SID':'', #Hidden field
    'FormNumber':'FROM_PYTHON1030PM',
    'RevisionNumber':'5',
    'FormType':'H(num)',
    'fsubmit':'Save Page'
    }
username = 'USERNAME'
password = 'PASSWORD'

headers = { 
    'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Charset' : 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
    'Accept-Encoding' : 'gzip,deflate,sdch',
    'Accept-Language' : 'en-US,en;q=0.8',
    'User-Agent' :  'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)', 
    'Authorization': 'Basic %s' % base64.encodestring('%s:%s' % (username, password)),
    'Cache-Control' : 'max-age=0',
    'Connection' : 'keep-alive',
    'Content-Type' : 'application/x-www-form-urlencoded',
    'Cookie' : 'ID=201399',
    'Host' : 'our_company_page.com',
    'Origin' : 'http://our_company_page.com',
    'Referer' : 'http://our_company_page.com/adm/add_form.php',
    'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, ' 
            'like Gecko) Chrome/26.0.1410.43 Safari/537.31'
    }

data = urllib.urlencode(values)
req = urllib2.Request(url, data, headers)
response = urllib2.urlopen(req)
print response.read()

Как вы можете видеть, я добавил заголовок, присутствующий на вкладке "Сеть Chrome", в запрос POST в urllib2.

Одно из изменений в версии Mechainze заключается в том, что я теперь напрямую обращаюсь к странице add_form.php, добавив соответствующий куки файл в свой запрос.

Однако даже при дублировании все, что я могу, у меня все еще есть одна и та же проблема: ответ - это та же самая страница, на которой я начал: никаких ошибок, сообщений об успешном выполнении, никаких изменений на сервере, просто вернулся к пустому форма.

Заключительный шаг: Отчаяние сидит, я устанавливаю WireShark

Время, чтобы сделать некоторое обнюхивание трафика. Я полна решимости видеть, что WTF происходит в этом магическом письме!

Я загружаю, устанавливаю и запускаю Wireshark. Я фильтрую для http, а затем сначала отправлю форму вручную в браузере, а затем запустите мой код с попытками отправить форму программно.

Это сетевой трафик:

Браузер:

enter image description here

Python:

enter image description here

Помимо того, что заголовки находятся в несколько ином порядке (это имеет значение), они выглядят точно так же!

Итак, где я, совершенно смущен, почему запрос post, который (насколько я могу судить), почти идентичный тому, который сделан браузером, не вносит никаких изменений на сервер.

Кто-нибудь когда-нибудь сталкивался с чем-либо подобным? Мне что-то не хватает? Что здесь происходит?


Изменить

В соответствии с предложением Рика я точно воспроизвел данные post. Я копирую его прямо со вкладки "Источник сети" в Chrome.

Измененный код выглядит следующим образом

data = 'SegmentID=&Segment=FROMPYTHON&SegmentPosition=1&SegmentContains=Sections&fsubmit=Save+Page'
req = urllib2.Request(url, data, headers)
response = urllib2.urlopen(req)
print response.read()

Единственное, что я изменил, это значение Segment от FROMBROWSER до FROMPYTHON.

К сожалению, это все равно дает тот же результат. Ответ - та же самая страница, с которой я начал.

Update


работает, но не решен

Я проверил библиотеку requests, продублировал свои усилия с помощью их API, и вот это волшебство! Пост действительно прошел. Остается вопрос: почему!? Я снова сделал еще один снимок с wirehark, и, насколько я могу судить, он точно такой же, как POST, сделанный из браузера.

Код

def post(eventID, name, pos, containsID):

    segmentContains = ["Sections", "Products"]
    url = 'http://my_site.com/adm/add_page.php'
    cookies = dict(EventID=str(eventID))
    payload = { "SegmentID" : "",
                "FormNumber" : name,
                "RevisionNumber" : str(pos),
                "FormType" : containsID,
                "fsubmit" : "Save Page"
            }

    r = requests.post(
            url, 
            auth=(auth.username, auth.password),
            allow_redirects=True,
            cookies=cookies,
            data=payload) 

Выход Wireshark


Запросы

enter image description here

Браузер

enter image description here

Итак, суммируем текущее состояние вопроса. Это работает, но я ничего не изменил. Я не знаю, почему попытки с Mechanize и urllib2 не удались. Что происходит, что позволяет фактически requests POST?

Изменить - предложение Wing Tang Wong:

В Wing Tand Wongs, я создал обработчик cookie и привязал его к urllib.opener. Таким образом, больше нет файлов cookie в заголовках вручную - на самом деле, я вообще ничего не назначаю.

Я сначала подключаюсь к странице adm, у которой есть ссылка на форму, а не сразу соединяется с формой.

'http://my_web_page.com/adm/segments.php?&n=201399'

Это дает cookie ID для моего urllib cookieJar. С этого момента я перехожу по ссылке на страницу, которая имеет форму, а затем пытаюсь подчиниться ей, как обычно.

Полный код:

url = 'http://my_web_page.com/adm/segments.php?&n=201399'
post_url = 'http://my_web_page.com/adm/add_page.php'
values = {
    'SegmentID':'',
    'Segment':'FROM_PYTHON1030PM',
    'SegmentPosition':'5',
    'SegmentContains':'Products',
    'fsubmit':'Save Page'
    }
username = auth.username
password = auth.password

headers = { 
    'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Charset' : 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
    'Accept-Encoding' : 'gzip,deflate,sdch',
    'Accept-Language' : 'en-US,en;q=0.8',
    'User-Agent' :  'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)', 
    'Authorization': 'Basic %s' % base64.encodestring('%s:%s' % (username, password)),
    'Cache-Control' : 'max-age=0',
    'Connection' : 'keep-alive',
    'Content-Type' : 'application/x-www-form-urlencoded',
    'Host' : 'mt_site.com',
    'Origin' : 'http://my_site.com',
    'Referer' : 'http://my_site.com/adm/add_page.php',
    'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.43 Safari/537.31'
    }

COOKIEFILE = 'cookies.lwp'
cj = cookielib.LWPCookieJar()

if os.path.isfile(COOKIEFILE):
    cj.load(COOKIEFILE)

opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
urllib2.install_opener(opener)

data = urllib.urlencode(values)
req = urllib2.Request(url, headers=headers)
handle = urllib2.urlopen(req)

req = urllib2.Request(post_url, data, headers)
handle = urllib2.urlopen(req)

print handle.info()
print handle.read()
print
if cj:
    print 'These are the cookies we have received so far :'
    for index, cookie in enumerate(cj):
        print index, '  :  ', cookie
    cj.save(COOKIEFILE)

То же, что и раньше. На сервере изменений не происходит. Чтобы убедиться, что файлы cookie действительно есть, я печатаю их на консоль после отправки формы, которая дает результат:

These are the cookies we have received so far :
<Cookie EventID=201399 for my_site.com/adm>  

Итак, файл cookie существует, и он был отправлен вместе с запросом. Так что все еще не уверен, что происходит.

Ответ 1

Прочитайте и перечитайте свой пост, а другие люди ответят несколько раз. Мои мысли:

Когда вы реализовали в mechanize и urllib2, похоже, что файлы cookie были жестко закодированы в ответ заголовка. Это, скорее всего, приведет к тому, что форма вышвырнет вас.

Когда вы переключились на использование веб-браузера и с помощью библиотеки запросов "python", куки файлы и обработка сеансов позаботились о них за кулисами.

Я считаю, что если вы измените свой код, чтобы учесть состояния cookie и сеанса, т.е. предположим автоматизированный сеанс при запуске, имеет пустой файл cookie для сайта и данные сеанса, но правильно отслеживает и управляет им во время сеанса, он должен работать.

Простое копирование и замена данных заголовка не будет работать, а правильно закодированный сайт должен отскочить назад к началу.

Не видя код бэкэнд для веб-сайта, это мое наблюдение. Cookies и данные сеанса являются виновниками.

Edit:

Нашел эту ссылку: http://docs.python-requests.org/en/latest/

Что описывает доступ к сайту с помощью аутентификации /etc. Формат аутентификации аналогичен используемой вами реализации запросов. Они ссылаются на источник git для реализации urllib2, который делает то же самое, и я заметил, что биты аутентификации отличаются от того, как вы выполняете биты auth:

https://gist.github.com/kennethreitz/973705

со страницы:

password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
password_manager.add_password(None, gh_url, 'user', 'pass')

auth_manager = urllib2.HTTPBasicAuthHandler(password_manager)
opener = urllib2.build_opener(auth_manager)

Интересно, измените ли вы способ реализации битов аутентификации для реализации urllib2, чтобы он работал.

Ответ 2

Я думаю, что PHP script является ошибкой и не отображает ничего, потому что ваши данные формы не совсем идентичны. Попробуйте повторить запрос на отправку, чтобы быть полностью идентичным, включая все значения. Я вижу, что текстовые данные на основе вашего экрана Wireshark для браузера включают в себя такие параметры, как SegmentPosition, который равен 0, но на вашем скриншоте Python нет значения для SegmentPosition. Формат некоторых параметров, таких как сегмент, отличается между браузером и запросом Python, который может вызывать ошибку, когда он пытается ее проанализировать.