ボンネットの䞋を怜玢したす。

むンタヌネット䞊の情報を怜玢する機胜は䞍可欠です。 お気に入りの怜玢゚ンゞンで「怜玢」ボタンをクリックするず、1秒埌に答えが埗られたす。







ほずんどの人は「裏偎」で䜕が起こっおいるのかたったく考えおいたせんが、怜玢゚ンゞンは有甚なツヌルであるだけでなく、耇雑な技術補品でもありたす。 最新の怜玢システムは、ビッグデヌタ、グラフおよびネットワヌク理論、自然蚀語テキスト分析、機械孊習、パヌ゜ナラむれヌション、ランキングなど、コンピュヌタヌ業界の高床な成果のほずんどすべおを䜿甚しおいたす。 怜玢゚ンゞンがどのように機胜するかを理解するこずで、技術開発のレベルを知るこずができるため、どの゚ンゞニアでもこれを理解するのに圹立ちたす。













いく぀かの蚘事では、怜玢゚ンゞンがどのように機胜するかを段階的に説明し、さらに、説明のために、根拠のないように独自の小さな怜玢゚ンゞンを構築したす。 もちろん、この怜玢゚ンゞンは「トレヌニング」され、GoogleたたはYandexの内郚で起こっおいるこずを非垞に匷力に単玔化したすが、䞀方で、あたり単玔化したせん。







最初のステップはデヌタ収集たたは、クロヌルずも呌ばれたすです。







りェブはグラフです



私たちが興味を持っおいるむンタヌネットの郚分はりェブペヌゞで構成されおいたす。 怜玢システムがナヌザヌの芁求で特定のWebペヌゞを芋぀けるこずができるようにするには、そのようなペヌゞが存圚し、芁求に関連する情報が含たれおいるこずを事前に知る必芁がありたす。 ナヌザヌは通垞、怜玢゚ンゞンからWebペヌゞの存圚に぀いお孊習したす。 怜玢゚ンゞンはWebペヌゞの存圚をどのように知るのですか 結局のずころ、誰も圌女にこれを明瀺的に報告する矩務はありたせん。







幞いなこずに、Webペヌゞはそれ自䜓では存圚せず、互いにリンクしおいたす。 怜玢ロボットはこれらのリンクをたどり、すべおの新しいWebペヌゞを発芋できたす。







実際、ペヌゞの構造ずペヌゞ間のリンクは、「グラフ」ず呌ばれるデヌタ構造を蚘述しおいたす。 定矩によるグラフは、頂点この堎合はWebペヌゞず゚ッゞこの堎合は頂点間のリンク、ハむパヌリンクで構成されたす。







グラフの他の䟋は、゜ヌシャルネットワヌク人々-ピヌク、゚ッゞ-友情関係、ロヌドマップ郜垂-ピヌク、゚ッゞ-郜垂間の道路、さらにはチェスのすべおの可胜な組み合わせチェスの組み合わせが頂点であり、ピヌク間の゚ッゞが存圚する堎合 1぀の動きで1぀の䜍眮から別の䜍眮に移動できたす。







グラフは、方向が゚ッゞに瀺されおいるかどうかに応じお、方向付けられおいたす。 ハむパヌリンクは䞀方向にしか移動できないため、むンタヌネットは有向グラフです。







さらに説明するために、むンタヌネットは匷力に接続されたグラフであるず想定したす。぀たり、むンタヌネット䞊のどこからでも開始するこずで、他のポむントに到達できるず仮定したす。 この仮定は明らかに間違っおいたすどこからでもリンクされない新しいWebペヌゞを簡単に䜜成できるため、そこにアクセスするこずはできたせん。リンクは怜玢にはあたり関心がありたせん。







Webグラフのごく䞀郚













グラフトラバヌサルアルゎリズム幅ず深さの怜玢



深さ怜玢



2぀の叀兞的なグラフトラバヌサルアルゎリズムがありたす。 最初の-シンプルで匷力な-アルゎリズムは、深さ優先怜玢DFSず呌ばれたす。 再垰に基づいおおり、次の䞀連のアクションを衚したす。







  1. 凊理された頂点の珟圚の頂点をマヌクしたす。
  2. 珟圚の頂点を凊理したす怜玢ロボットの堎合、凊理は単にコピヌを保存したす。
  3. 珟圚の頂点から移動できるすべおの頂点に぀いお、頂点がただ凊理されおいない堎合、再垰的に凊理したす。


このアプロヌチを文字通り実装するPythonコヌドは次のずおりです。







seen_links = set() def dfs(url): seen_links.add(url) print('processing url ' + url) html = get(url) save_html(url, html) for link in get_filtered_links(url, html): if link not in seen_links: dfs(link) dfs(START_URL)
      
      





完党なgithubコヌド







ほが同じ方法で、たずえば、暙準のLinux wgetナヌティリティは-rパラメヌタヌを䜿甚しお機胜したす。これは、サむトを再垰的にポンプアりトする必芁があるこずを瀺したす。







 wget -r habrahabr.ru
      
      





深さ怜玢方法は、小さなサむトのWebペヌゞをクロヌルするために適切に適甚されたすが、むンタヌネット党䜓をクロヌルするにはあたり䟿利ではありたせん。







  1. その䞭に含たれる再垰呌び出しは、あたりうたく䞊行しおいたせん。
  2. この実装を䜿甚するず、アルゎリズムはリンクをどんどん深くなり、最終的には再垰呌び出しスタックに十分なスペヌスがなくなる可胜性が高くなり、スタックオヌバヌフロヌ゚ラヌが発生したす。


䞀般に、これらの問題は䞡方ずも解決できたすが、代わりに別の叀兞的なアルゎリズムである幅優先探玢を䜿甚したす。







広い怜玢



幅優先怜玢BFSは深さ怜玢ず同様の方法で機胜したすが、開始ペヌゞからの距離の順にグラフの䞊郚を回りたす。 このため、アルゎリズムは「キュヌ」デヌタ構造を䜿甚したす。キュヌでは、芁玠を最埌に远加し、最初からそれらを取埗できたす。







  1. アルゎリズムは次のように説明できたす。
  2. 最初のピヌクをキュヌず倚くの「衚瀺された」ピヌクに远加したす。
  3. キュヌが空でない堎合、凊理のためにキュヌから次の頂点を取埗したす。
  4. トップを加工したす。
  5. 「衚瀺された」頂点に含たれおいない、凊理された頂点からのすべおの゚ッゞ

    1. 「seen」に远加したす。
    2. キュヌに远加したす。
  6. ステップ2に進みたす。


Pythonコヌド







 def bfs(start_url): queue = Queue() queue.put(start_url) seen_links = {start_url} while not (queue.empty()): url = queue.get() print('processing url ' + url) html = get(url) save_html(url, html) for link in get_filtered_links(url, html): if link not in seen_links: queue.put(link) seen_links.add(link) bfs(START_URL)
      
      





完党なgithubコヌド













キュヌには、最初に最初のリンクから1぀のリンク、次に2぀のリンク、次に3぀のリンクなどの距離にある頂点がありたす。぀たり、幅優先探玢アルゎリズムは垞に最短パスで頂点に到達したす。







もう1぀の重芁なポむントこの堎合、キュヌず「芋られる」ピヌクの倚くは単玔なむンタヌフェむス远加、取埗、゚ントリのチェックのみを䜿甚し、これらのむンタヌフェむスを介しおクラむアントず通信する別のサヌバヌに簡単に移動できたす。 この機胜により、 マルチスレッドグラフトラバヌサルを実装できたす。同じキュヌを䜿甚する耇数のプロセッサを同時に実行できたす。







Robots.txt



実際の実装を説明する前に、適切に動䜜するクロヌラヌは、robots.txtファむルでWebサむトの所有者によっお蚭定された犁止事項を考慮しおいるこずに泚意したいず思いたす。 たずえば、lenta.ruのrobots.txtの内容は次のずおりです。







 User-agent: YandexBot Allow: /rss/yandexfull/turbo User-agent: Yandex Disallow: /search Disallow: /check_ed Disallow: /auth Disallow: /my Host: https://lenta.ru User-agent: GoogleBot Disallow: /search Disallow: /check_ed Disallow: /auth Disallow: /my User-agent: * Disallow: /search Disallow: /check_ed Disallow: /auth Disallow: /my Sitemap: https://lenta.ru/sitemap.xml.gz
      
      





ここでは、Yandexロボット、Google、その他すべおの人を蚪問するこずを犁じられおいるサむトのいく぀かのセクションが定矩されおいるこずがわかりたす。 Pythonでrobots.txtの内容を考慮するために、暙準ラむブラリに含たれるフィルタヌの実装を䜿甚できたす。







 In [1]: from urllib.robotparser import RobotFileParser ...: rp = RobotFileParser() ...: rp.set_url('https://lenta.ru/robots.txt') ...: rp.read() ...: In [3]: rp.can_fetch('*', 'https://lenta.ru/news/2017/12/17/vivalarevolucion/') Out[3]: True In [4]: rp.can_fetch('*', 'https://lenta.ru/search?query=big%20data#size=10|sort=2|domain=1 ...: |modified,format=yyyy-MM-dd') Out[4]: False
      
      





実装



そのため、むンタヌネットを䞀呚し、さらに凊理するために保存する必芁がありたす。







もちろん、デモンストレヌションのために、むンタヌネット党䜓を回っお保存するこずはできたせん-非垞に高䟡ですが、朜圚的にむンタヌネット党䜓のサむズに拡倧瞮小できるずいう事実を考慮しおコヌドを開発したす。







これは、同時に倚数のサヌバヌで䜜業し、結果を簡単に凊理できる䜕らかの皮類のストレヌゞに保存する必芁があるこずを意味したす。

゜リュヌションの基瀎ずしおAmazon Web Servicesを遞択したした。特定の数のマシンを簡単に䜜成し、結果を凊理しお、 Amazon S3分散ストレヌゞに保存できるからです。 同様の゜リュヌションは、たずえばgoogle 、 microsoftおよびYandexです。













開発した゜リュヌションのアヌキテクチャ



私のデヌタ収集スキヌムの䞭心的な芁玠はキュヌサヌバヌです。このサヌバヌには、ダりンロヌドしお凊理するURLのキュヌず、プロセッサが既に「芋た」倚くのURLが栌玍されおいたす。 私の実装では、それらは最も単玔なキュヌに基づいおおり、Pythonデヌタ構造を蚭定しおいたす。







実際の本番システムでは、おそらくそれらの代わりに、キュヌたずえば、 kafka およびセットの分散ストレヌゞたずえば、 erospikeなどのデヌタベヌスのメモリ内キヌ倀クラスの゜リュヌションが適切です に既存の゜リュヌションを䜿甚する䟡倀がありたす。 これにより、完党な氎平スケヌラビリティを実珟できたすが、䞀般的にキュヌサヌバヌの負荷はそれほど倧きくないため、私の小さなデモプロゞェクトでは、このような小芏暡では意味がありたせん。







皌働䞭のサヌバヌは、ダりンロヌド甚の新しいURLグルヌプを定期的に遞択しキュヌに䞍必芁な負担をかけないように、すぐに倚くを取り陀きたす、Webペヌゞをダりンロヌドし、s3に保存し、新しいキュヌをダりンロヌドキュヌに远加したす。







URLを远加する負担を軜枛するために、远加もグルヌプで行われたすWebペヌゞで芋぀かったすべおの新しいURLを䞀床に远加したす。 たた、䜜業ノヌドの偎で既に远加されたペヌゞを事前にフィルタリングするために、倚くの「衚瀺された」URLを運甚サヌバヌず定期的に同期したす。







ダりンロヌドしたWebペヌゞを分散クラりドストレヌゞS3に保存したす-これは埌で分散凊理に䟿利です。







キュヌは、远加および凊理されたリク゚ストの数に関する統蚈を統蚈サヌバヌに定期的に送信したす。 䜜業ノヌドごずに合蚈で個別に統蚈を送信したす。これは、ダりンロヌドが正垞に行われおいるこずを明確にするために必芁です。 個々の䜜業マシンのログを読み取るこずは䞍可胜なので、チャヌトで動䜜を監芖したす。 ダりンロヌドを監芖するための゜リュヌションずしお、 グラファむトを遞択したした。







クロヌラヌの打ち䞊げ



すでに曞いたように、むンタヌネット党䜓をダりンロヌドするには膚倧なリ゜ヌスが必芁なので、そのほんの䞀郚、぀たりhabrahabr.ruずgeektimes.ruずいうサむトに限定したした。 ただし、制限はかなり条件付きであり、他のサむトに拡匵するこずは、利甚可胜な鉄の量の問題です。 実行するために、Amazonクラりドに新しいクラスタヌを䜜成するシンプルなスクリプトを実装し、そこに゜ヌスコヌドをコピヌしお、察応するサヌビスを開始したす。







 #deploy_queue.py from deploy import * def main(): master_node = run_master_node() deploy_code(master_node) configure_python(master_node) setup_graphite(master_node) start_urlqueue(master_node) if __name__ == main(): main()
      
      





 #deploy_workers.py #run as: http://<queue_ip>:88889 from deploy import * def main(): master_node = run_master_node() deploy_code(master_node) configure_python(master_node) setup_graphite(master_node) start_urlqueue(master_node) if __name__ == main(): main()
      
      





呌び出されたすべおの関数を含むdeploy.py スクリプトのコヌド







統蚈ツヌルずしおグラファむトを䜿甚するず、矎しいグラフを描くこずができたす。







赀いグラフは怜出されたURLを瀺し、緑のグラフはダりンロヌドされたURLを瀺し、青いグラフはキュヌ内のURLを瀺したす。 期間党䜓で、550䞇ペヌ​​ゞがダりンロヌドされたした。









䜜業ノヌドごずに分類された、1分あたりのクロヌルされたペヌゞの数。 グラフは䞭断されず、クロヌルは通垞モヌドになりたす。







結果



habrahabrずgeektimesのダりンロヌドには3日かかりたした。

各䜜業マシンのワヌカヌのむンスタンス数を増やし、ワヌカヌの数を増やすこずで、はるかに高速にダりンロヌドするこずができたすが、ハブ自䜓の負荷は非垞に倧きくなりたす。お気に入りのサむトで問題が発生するのはなぜですか

その過皋で、クロヌラヌにいく぀かのフィルタヌを远加し、怜玢゚ンゞンの開発に関係のない明らかに䞍芁なペヌゞを陀倖し始めたした。







開発されたクロヌラヌはデモですが、䞀般にスケヌラブルであり、同時に倚数のサむトから倧量のデヌタを収集するために䜿甚できたすただし、実皌働環境では 、 heritrixなどの既存のクロヌル゜リュヌションに焊点を圓おるこずが理にかなっおいたす。䞀床だけではなく、定期的に起動し、倚くの远加機胜を実装する必芁がありたすが、これたでは無芖しおいたした。







クロヌラヌの時代、私はAmazonクラりドに玄60ドルを費やしたした。 合蚈550䞇ペヌ​​ゞをダりンロヌドし、合蚈ボリュヌムは668ギガバむトです。







シリヌズの次回の蚘事では、ビッグデヌタ技術を䜿甚しおダりンロヌドしたWebペヌゞにむンデックスを䜜成し、ダりンロヌドしたペヌゞで実際に怜玢するための最も簡単な゚ンゞンを蚭蚈したす。







プロゞェクトコヌドはgithubで入手できたす










All Articles