pythonで爬虫類を書くテクニック

66634 ワード

pythonを学ぶのも3ヶ月余りあって、最も多く使ったのはやはり各種の爬虫類のスクリプトです:エージェントの本機の検証をつかむスクリプトを書いたことがあって、discuzフォーラムの中で自動的に登録して自動的に貼るスクリプトを書いたことがあって、自動的にメールを受け取るスクリプトを書いたことがあって、簡単な検証コードの識別のスクリプトを書いたことがあって、本はgoogle musicのキャプチャスクリプトを書きたいと思って、結果は強大なgmboxがあって、書く必要はありません.
これらのスクリプトには共通性があり、webに関連しているので、リンクを取得する方法が必要です.simplecdという半爬虫半サイトのプロジェクトを加えて、多くの爬虫類が駅をつかんだ経験を蓄積しています.ここでまとめてみると、これから何をするにも労働を繰り返す必要はありません.
1.最も基本的な掴み所
import urllib2
content = urllib2.urlopen('http://XXXX').read()

2.プロキシサーバーの使用
これは、IPが閉鎖されたり、IPアクセスの回数が制限されたりするなど、場合によっては有用である.
import urllib2
proxy_support = urllib2.ProxyHandler({'http':'http://XX.XX.XX.XX:XXXX'})
opener = urllib2.build_opener(proxy_support, urllib2.HTTPHandler)
urllib2.install_opener(opener)
content = urllib2.urlopen('http://XXXX').read()

3.ログインが必要な場合
ログインの場合は面倒ですが、質問を分割します.
3.1クッキーの処理
import urllib2, cookielib
cookie_support= urllib2.HTTPCookieProcessor(cookielib.CookieJar())
opener = urllib2.build_opener(cookie_support, urllib2.HTTPHandler)
urllib2.install_opener(opener)
content = urllib2.urlopen('http://XXXX').read()

はい、そうです.エージェントとクッキーを同時に使いたいならproxy_に参加します.次にopernerを
opener = urllib2.build_opener(proxy_support, cookie_support, urllib2.HTTPHandler)

3.2フォームの処理
ログインには必要な表を記入しますが、フォームはどのように記入しますか?まず、ツールを使用して、記入する表の内容を切り取ります.
例えばfirefox+httpfoxプラグインで自分がどんなパッケージを送ったのか見てみましょう
これを例に挙げると、verycdを例に、まず自分が送ったPOSTリクエストとPOSTフォームの項目を見つけます.
verycdが見える場合はusername,password,continueURI,fk,login_を記入する必要がありますsubmitは、fkがランダムに生成されている(実際にはあまりランダムではなく、epoch時間を簡単な符号化で生成しているように見える)ため、Webページから取得する必要があります.つまり、一度Webページにアクセスし、正規表現などのツールで戻りデータのfk項目を切り取る必要があります.continueURIはその名の通り自由に書けます、login_submitは固定されており、これはソースコードから見ることができます.usernameもありますが、passwordは明らかです.
はい、記入するデータがあればpostdataを生成します.
import urllib
postdata=urllib.urlencode({
    'username':'XXXXX',
    'password':'XXXXX',
    'continueURI':'http://www.verycd.com/',
    'fk':fk,
    'login_submit':'  '
})

次にhttpリクエストを生成し、リクエストを送信します.
req = urllib2.Request(
    url = 'http://secure.verycd.com/signin/*/http://www.verycd.com/',
    data = postdata
)
result = urllib2.urlopen(req).read()

3.3ブラウザアクセスの偽装
一部のサイトは爬虫類の訪問に反感を持っており、爬虫類に対してはすべて要求を拒否している.httpパッケージのヘッダーを変更することで、ブラウザに偽装する必要があります.
headers = {
    'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'
}
req = urllib2.Request(
    url = 'http://secure.verycd.com/signin/*/http://www.verycd.com/',
    data = postdata,
    headers = headers
)

3.4反「反盗賊チェーン」
いくつかのサイトにはいわゆる盗難防止チェーンの設定がありますが、実は簡単に着ています.あなたが要求したヘッダーの中をチェックすることです.refererサイトは彼自身ではありません.だから、3.3のようにヘッダーのrefererをこのサイトに変更するだけでいいです.黒幕で有名なcnbetaを例に挙げます.
headers = {
    'Referer':'http://www.cnbeta.com/articles'
}

headersはdictデータ構造で、任意の希望のheaderを入れて、いくつかの偽装をすることができます.例えば、一部の自業自得のウェブサイトはいつも人のプライバシーを覗くのが好きで、他の人は代理訪問を通じて、彼はどうしてもheaderの中のX-Forwarded-Forを読んで人の本当のIPを見に来て、何も言わないで、それでは直接X-Forwarde-Forを変えて、何か面白いものに変えて彼をいじめて、ほほほ.
3.5究極の絶技
時には3.1-3.4をしても訪問は根拠にされることがありますが、仕方なく、httpfoxで見たheadersを正直に全部書いておけば、普通でいいです.これ以上だめなら、究極の絶技を使うしかない.seleniumはブラウザを直接制御してアクセスし、ブラウザができる限り、それもできる.似たようなのはpamie、watirなどです.
4.マルチスレッド同時キャプチャ
単一スレッドが遅すぎるとマルチスレッドが必要になりますが、ここでは簡単なスレッドプールテンプレートというプログラムに1-10を簡単に印刷しただけですが、同時であることがわかります.
from threading import Thread
from Queue import Queue
from time import sleep
#q     
#NUM       
#JOBS      
q = Queue()
NUM = 2
JOBS = 10
#       ,        
def do_somthing_using(arguments):
    print arguments
#       ,             
def working():
    while True:
        arguments = q.get()
        do_somthing_using(arguments)
        sleep(1)
        q.task_done()
#fork NUM       
for i in range(NUM):
    t = Thread(target=working)
    t.setDaemon(True)
    t.start()
# JOBS    
for i in range(JOBS):
    q.put(i)
#    JOBS  
q.join()

5.認証コードの処理
認証コードに遭遇したらどうしますか?ここでは2つの状況に分けて処理します.
Googleの認証コード、冷やし

簡単な検証コード:文字の個数が限られており、単純な平行移動や回転にノイズを加えて歪まないものしか使用されていない.次に検証コードとフィーチャーライブラリを比較します.これは比较的に复雑で、1篇の博文は言い终わらないで、ここは展开しないで、具体的なやり方は本の関连する教科书をいじってよく研究してください.

実際には検証コードが弱いものもありますが、ここでは点呼しません.どうせ私は2の方法で精度の高い検証コードを抽出したことがあるので、2は実際に実行できます.

6 gzip/deflateサポート
現在のウェブページではgzip圧縮が一般的にサポートされており、これは多くの伝送時間を解決することが多い.VeryCDのホームページを例にとると、非圧縮バージョン247 Kは、後45 Kを圧縮し、元の1/5となっている.これは、キャプチャ速度が5倍速くなることを意味します.
しかしpythonのurllib/urllib 2はデフォルトでは圧縮をサポートしていません.圧縮フォーマットを返すには、requestのヘッダに「accept-encoding」と明記し、responseを読み込んだ後、ヘッダが「content-encoding」の項目を確認して復号が必要かどうかを判断する必要があります.煩雑です.urllib 2がgzip、defalteを自動的にサポートするにはどうすればいいですか?
BaseHanlderクラスを継承してbuild_Opener方式で処理:
import urllib2
from gzip import GzipFile
from StringIO import StringIO
class ContentEncodingProcessor(urllib2.BaseHandler):
  """A handler to add gzip capabilities to urllib2 requests """
 
  # add headers to requests
  def http_request(self, req):
    req.add_header("Accept-Encoding", "gzip, deflate")
    return req
 
  # decode
  def http_response(self, req, resp):
    old_resp = resp
    # gzip
    if resp.headers.get("content-encoding") == "gzip":
        gz = GzipFile(
                    fileobj=StringIO(resp.read()),
                    mode="r"
                  )
        resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code)
        resp.msg = old_resp.msg
    # deflate
    if resp.headers.get("content-encoding") == "deflate":
        gz = StringIO( deflate(resp.read()) )
        resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code)  # 'class to add info() and
        resp.msg = old_resp.msg
    return resp
 
# deflate support
import zlib
def deflate(data):   # zlib only provides the zlib compress format, not the deflate format;
  try:               # so on top of all there's this workaround:
    return zlib.decompress(data, -zlib.MAX_WBITS)
  except zlib.error:
    return zlib.decompress(data)

そして簡単に、
encoding_support = ContentEncodingProcessor
opener = urllib2.build_opener( encoding_support, urllib2.HTTPHandler )
 
#   opener    ,       gzip/defalte      
content = opener.open(url).read()

7.より便利なマルチスレッド
まとめてみると、簡単なマルチスレッドテンプレートに言及していますが、その東東は本当にプログラムに適用されると、プログラムが支離滅裂になり、目に入らないだけです.マルチスレッドをどのように簡単に行うかについても頭を働かせました.まず、マルチスレッド呼び出しをどのように行うのが一番便利か考えてみましょう.
1、twistedによる非同期I/Oキャプチャ
実際、より効率的なキャプチャは必ずしもマルチスレッドを使用する必要はありません.非同期I/O法を使用することもできます.twistedのgetPage法を直接使用し、非同期I/O終了時のcallbackとerrback法をそれぞれ加えればいいです.たとえば、次のようにできます.
from twisted.web.client import getPage
from twisted.internet import reactor
 
links = [ 'http://www.verycd.com/topics/%d/'%i for i in range(5420,5430) ]
 
def parse_page(data,url):
    print len(data),url
 
def fetch_error(error,url):
    print error.getErrorMessage(),url
 
#       
for url in links:
    getPage(url,timeout=5) \
        .addCallback(parse_page,url) \ #     parse_page  
        .addErrback(fetch_error,url)     #     fetch_error  
 
reactor.callLater(5, reactor.stop) #5     reactor    
reactor.run()

twisted人はその名の通り、書かれたコードがあまりにも歪んでいて、普通の人では受け入れられません.この簡単な例はまあまあですが.twistedを書くたびにプログラム全体が歪んで、疲れてたまらない.ドキュメントはないに等しい.ソースコードを見なければならない.
gzip/deflateをサポートしたり、ログインの拡張をしたりするには、twistedのために新しいHTTPClientFactoryなどを書かなければなりません.私の眉は本当にしわで、諦めました.根性のある人は自分でやってみてください.
このtwistedで一括サイト処理を行う方法を説明する文章はいいですね.浅くて深くて、深くて浅くて、見てもいいです.
2、簡単なマルチスレッドキャプチャクラスを設計する
やはりurllibのようなpython「本土」の中で振り回されたほうが気持ちがいいと思います.考えてみてくださいFetcherクラスがあれば、このように呼び出すことができます.
f = Fetcher(threads=10) #        10
for url in urls:
    f.push(url)  #   url      
while f.taskleft(): #           
    content = f.pop()  #            
    do_with(content) #   content  

このようなマルチスレッド呼び出しは簡単明瞭であるので、このように設計しましょう.まず2つのキューがあり、Queueで解決しなければなりません.マルチスレッドの基本アーキテクチャも「テクニックのまとめ」と似ています.push方法もpop方法も比較的扱いやすく、Queueの方法を直接使用しています.taskleftは「実行中のタスク」や「キュー内のタスク」があれば、そうです.コードは次のとおりです.
import urllib2
from threading import Thread,Lock
from Queue import Queue
import time
 
class Fetcher:
    def __init__(self,threads):
        self.opener = urllib2.build_opener(urllib2.HTTPHandler)
        self.lock = Lock() #   
        self.q_req = Queue() #    
        self.q_ans = Queue() #    
        self.threads = threads
        for i in range(threads):
            t = Thread(target=self.threadget)
            t.setDaemon(True)
            t.start()
        self.running = 0
 
    def __del__(self): #            
        time.sleep(0.5)
        self.q_req.join()
        self.q_ans.join()
 
    def taskleft(self):
        return self.q_req.qsize()+self.q_ans.qsize()+self.running
 
    def push(self,req):
        self.q_req.put(req)
 
    def pop(self):
        return self.q_ans.get()
 
    def threadget(self):
        while True:
            req = self.q_req.get()
            with self.lock: #          ,  critical area
                self.running += 1
            try:
                ans = self.opener.open(req).read()
            except Exception, what:
                ans = ''
                print what
            self.q_ans.put((req,ans))
            with self.lock:
                self.running -= 1
            self.q_req.task_done()
            time.sleep(0.1) # don't spam
 
if __name__ == "__main__":
    links = [ 'http://www.verycd.com/topics/%d/'%i for i in range(5420,5430) ]
    f = Fetcher(threads=10)
    for url in links:
        f.push(url)
    while f.taskleft():
        url,content = f.pop()
        print url,len(content)

8.些細な経験
1、接続プール:
opener.Openとurllib 2.urlopenと同様にhttpリクエストが新規作成されます.通常、これは問題ではありません.線形環境では、1秒でリクエストが新たに生成される可能性があります.しかし、マルチスレッド環境では、1秒に数十以上のリクエストがあり、数分で正常な理性的なサーバが閉鎖されます.
しかし、通常のhtmlリクエストでは、サーバと数十個の接続を同時に維持するのは正常なことなので、HttpConnectionのプールを手動で維持し、捕まえるたびに接続プールから接続を選択して接続すればよい.
ここには巧みな方法があります.squidをエージェントサーバとしてキャプチャすると、squidは自動的に接続プールを維持し、データキャッシュ機能も付いています.そして、squidはもともと私のサーバーごとに必ずインストールされているものです.自分で面倒を見て接続プールを書く必要はありません.
2、スレッドのスタックサイズを設定する
スタックサイズの設定はpythonのメモリ使用量に著しく影響し、pythonマルチスレッドがこの値を設定しないとopenvzのvpsにとって非常に致命的なプログラム使用量になります.stack_sizeは32768より大きくなければなりませんが、実際には32768*2以上必要です.
from threading import stack_size
stack_size(32768*16)

3、設定に失敗したら自動的に再試行する
    def get(self,req,retries=3):
        try:
            response = self.opener.open(req)
            data = response.read()
        except Exception , what:
            print what,req
            if retries>0:
                return self.get(req,retries-1)
            else:
                print 'GET Failed',req
                return ''
        return data

4、タイムアウトの設定
    import socket
    socket.setdefaulttimeout(10) #  10      

5、上陸
ログインがさらに簡素化され、まずbuild_Openerではクッキーサポートを追加し、「まとめ」を参照します.VeryCDにログインするには、Fetcherに空のメソッドloginを追加し、init()で呼び出し、Fetcherクラスとoverride loginメソッドを継承します.
def login(self,username,password):
    import urllib
    data=urllib.urlencode({'username':username,
                           'password':password,
                           'continue':'http://www.verycd.com/',
                           'login_submit':u'  '.encode('utf-8'),
                           'save_cookie':1,})
    url = 'http://www.verycd.com/signin'
    self.opener.open(url,data).read()

するとFetcher初期化時にVeryCDサイトに自動的にログインします.
9.まとめ
このように、上記のすべての小さなテクニックを混ぜ合わせると、私の現在の私蔵最終版のFetcherクラスとは遠くなく、マルチスレッド、gzip/deflate圧縮、タイムアウト設定、自動再試行、スタックサイズの設定、自動ログインなどの機能をサポートしています.コードは簡単で、使いやすくて、性能も俗っぽくなくて、家で旅行して、人を殺して火をつけて、咳をして、必ず必要なツールと言えます.
最終版と遠くないのは、最終版にはもう一つの保存機能「ベスト術」:マルチエージェント自動選択があるからだ.ただのrandomのように見えますchoiceの違いには,エージェント取得,エージェント検証,エージェント速度測定など多くの部分が含まれているが,これはもう一つの物語である.
リファレンス
http://obmem.info/?p=476

http://obmem.info/?p=753