11

手作業ではなく、当社の管理ページのフォームにデータをプログラムで送信しようとしています。

私は、この Web サイトをスクレイピングしてデータを操作するツールを他にも数多く作成しました。しかし、何らかの理由で、この特定の 1 つが私に多く問題を引き起こしています。

ブラウザでのウォークスルー:

以下は、データをスクレイピングして投稿しようとしているページです。これらのページは通常 js シャドウ ボックスに表示されますが、Javascript を無効にしても問題なく機能することに注意してください。

(注、これは会社のページなので、すべてのフォーム フィールドをジャンク タイトルに置き換えました。たとえば、顧客番号は完全にでっち上げです)

また、ユーザー名/パスワードの壁の背後にある会社のページであるため、テスト用に Web サイトを提供することはできません。そのため、この投稿にできるだけ多くの詳細を挿入しようとしました!

主なエントリーポイントはこちら:

ここに画像の説明を入力

このページから をクリック"Add New form"すると、次のページが新しいタグで開きます (javascript が無効になっているため)。

ここに画像の説明を入力

このページで、小さなフォームに入力し、[送信] をクリックすると、次のページに成功メッセージが表示されます。

ここに画像の説明を入力

シンプルなはずですよね?

コード試行 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ボタンをクリックします。しかし、ここが厄介なところです。mechanize が返す応答は、フォームとまったく同じページです。エラー メッセージも成功メッセージも表示されず、管理ページを手動で確認しても変更はありません。

ネットワーク アクティビティの検査

イライラして、私は Chrome を開いてネットワーク タブを見ながら、手動でフォームに入力してブラウザで送信しました。

フォームを送信すると、ネットワーク アクティビティは次のようになります。

ここに画像の説明を入力

私にはかなり簡単に思えます。、css ファイル用の 、およびjquerypostライブラリ用の があります。ある種の画像用に別のものがありますが、それが何のためにあるのかわかりません。getgetget

POST リクエストの詳細を調べる:

ここに画像の説明を入力

スクレイピングの問題についてグーグルで調べた後、サーバーが特定のヘッダーを予期している可能性があるという提案を見ました。POST リクエストで作成されたものをすべてコピーし、どれが重要であるかがわかるまでゆっくりとヘッダーを削除する必要があります。 1。だから私はまさにそれを行い、[ネットワーク] タブのすべての情報をコピーして、投稿リクエストに貼り付けました。

コード試行 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 の Network タブにあるヘッダーを の POST リクエストに追加しましたurllib2

Mechainze バージョンからの追加の変更点の 1 つはadd_form.php、関連する Cookie をリクエストに追加することで、ページに直接アクセスできるようになったことです。

ただし、可能な限りすべてを複製しても、まったく同じ問題が発生します。応答は、開始したページとまったく同じです。エラーも成功メッセージも、サーバーでの変更もなく、空白のフォームに戻っただけです。

最後のステップ: 必死になって、WireShark をインストールします

トラフィック スニッフィングを行う時間です。この魔法のような投稿リクエストで WTF が行われていることを確認したいと思います!

Wireshark をダウンロードしてインストールし、起動します。をフィルタリングしてhttpから、まずブラウザでフォームを手動で送信し、コードを実行してフォームをプログラムで送信しようとします。

これはネットワーク トラフィックです。

ブラウザ:

ここに画像の説明を入力

パイソン:

ここに画像の説明を入力

ヘッダーの順序が少し異なることを除けば (それは問題ではありません)、それらはまったく同じに見えます!

それで、私が完全に混乱しているのpostは、(私が知る限り)ブラウザによって行われたものとほぼ同じであるリクエストが、サーバー上で変更を加えていない理由です。

誰もこのようなことに遭遇したことがありますか?明らかな何かが欠けていますか?何が起きてる?


編集

Ric の提案に従って、POSTデータを正確に複製しました。Chrome の Network Source タブから直接コピーします。

変更されたコードは次のようになります

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値だけでした。FROMBROWSERFROMPYTHON

残念ながら、これでも同じ結果が得られます。応答は、私が開始したのと同じページです。

アップデート


動作していますが、解決されていません

私はライブラリをチェックアウトし、requests彼らの API を使用して自分の努力を複製したところ、魔法のように機能しました! POSTは実際に通過しました。疑問が残ります:なぜ!? 私は再びwiresharkで別のスナップショットを撮りました.ブラウザから作成された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 出力


リクエスト

ここに画像の説明を入力

ブラウザ

ここに画像の説明を入力

それで、質問の現在の状態を要約します。それは機能しますが、実際には何も変わっていません。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'

これにより、IDCookie が myに渡されます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 はそこにあり、リクエストと一緒に送信されています。まだ何が起こっているのかわかりません。

4

2 に答える 2

4

あなたの投稿を読んで再読すると、他の人の回答が数回繰り返されます。私の考え:

mechanize と urllib2 で実装すると、Cookie がヘッダー レスポンスにハード コードされたように見えます。これにより、フォームがあなたを追い出す可能性が最も高くなります。

Web ブラウザを使用して Python の「リクエスト」ライブラリを使用するように切り替えたとき、Cookie とセッションの処理は舞台裏で処理されていました。

Cookieとセッションの状態を考慮してコードを変更すると、つまり. 開始時に自動化されたセッションがあり、サイトの Cookie が空で、セッション データがないことを前提としていますが、セッション中に適切に追跡および管理されている場合は、機能するはずです。

ヘッダー データを単純にコピーして置き換えるだけでは機能せず、適切にコーディングされたサイトを使用すると、最初に戻る必要があります。

ウェブサイトのバックエンド コードが表示されていない場合、上記は私の見解です。Cookie とセッション データが原因です。

編集:

このリンクを見つけました: http://docs.python-requests.org/en/latest/

認証などを使用してサイトにアクセスする方法について説明します。認証の形式は、使用している Requests 実装に似ています。それらは、同じことを行う urllib2 実装の git ソースにリンクしています。認証ビットが、認証ビットの実行方法とは異なることに気付きました。

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 実装の認証ビットを実装する方法を変更すると、それが機能するのではないかと思います。

于 2013-04-04T23:47:08.677 に答える
0

フォーム データが完全に同一ではないため、PHP スクリプトでエラーが発生し、何も表示されていないと思います。すべての値を含めて完全に同一になるように、投稿リクエストを複製してみてください。ブラウザの Wireshark スクリーンショットの行ベースのテキスト データには、0 である SegmentPosition などのパラメータが含まれていますが、Python スクリーンショットには SegmentPosition の値がありません。セグメントなどの一部のパラメーターの形式は、ブラウザーと Python 要求の間で異なるように見え、解析しようとするとエラーが発生する可能性があります。

于 2013-03-31T20:13:05.197 に答える