LoginSignup
3
2

More than 5 years have passed since last update.

はてな匿名ダイアリーの記事のURL1000件以上を3分くらいで取得する

Last updated at Posted at 2018-03-01

parallelとシェルスクリプトとrubularとirb超便利という記事です!!

tmp.rb
['mechanize','nokogiri','pp','benchmark'].each{|gem|require gem}

$REGION = 
'au melib.au aus bg bul br ca vanc.ca.west cp chil cr czech den egy fin
 fr frank.gr gre hk hg ice loc2.in ind ire iom isr it jp loc2.jp lv lux
 my mx md nl nz no pa pl por ro ru mos.ru singp loc2.singp saudi sp slk
 za sk swe tw thai turk tun uae uk lon.uk ukr fl.east.usa atl.east.usa
 ny.east.usa chi.central.usa dal.central.usa la.west.usa lv.west.usa
 lv.west.usa sa.west.usa nj.east.usa central.usa west.usa east.usa vn'

set_trace_func lambda{|event, file, line, id, binding, klass|
  return if not event == "line" && file ==  __FILE__
  # p "#{event} at #{klass}:#{id} #{file}:#{line}"
  p "#{event}:#{line} at #{klass}:#{id}"
}

class Crawler
  def self.random_proxies
    $REGION.split(' ').sample.+(".torguardvpnaccess.com")
  end

  def self.init(proxy_domain, port, email, password)
    @agent = Mechanize.new
    @agent.set_proxy(proxy_domain, port, email, password) rescue retry
  end

  def self.crawl(url,min,max)
    begin
    # Crawler::init(Crawler::random_proxies,6060,ENV['PROXY_USER'],ENV['PROXY_PASSWORD'])
    @agent = Mechanize.new
    result = ""
    (min..max).map{|id|result << @agent.get(url+"#{id}").body}
    result
    rescue
      retry
    end
  end

  def self.parse(html,min,max,blocks)
    @result =[]
    {html=>blocks}.each{|html,block|(min..max).map{|num|block.each{|b|@result << b.(num,html)}}}
    @result
  end
end


LIST_URL = "https://anond.hatelabo.jp/?mode=top&page="
min,max = ARGV[0].to_s,(ARGV[0].to_i+9).to_s
all_html = ""
all_html << Crawler::crawl(LIST_URL,min,max)
File.write('all_html.txt'+ARGV[0].to_s, all_html)

書き換えるの面倒だったけど、ソースを読むと俺が使ってるproxyサービスがバレてしまうというあれ。
あと、今回nokogiriとself.parse使ってないけどまぁいいや。
コメントアウト部分はproxy使うなら適宜書き換えて。
とにかく俺のクローリングすげーって話じゃなくてset_trace_func超便利(デバッグしやすい)とparallel超便利(速い)っていうのとシェルスクリプト超便利(超簡単)っていうのがメインの話。
parallelは普通のプログラムも並列で動かせるよっていう代物。詳細は下記参照。
http://bicycle1885.hatenablog.com/entry/2014/08/10/143612

実行はこんな感じ

% parallel ruby tmp.rb ::: 1 11 21 31 41

10秒(すごい!!速い!!)くらいでクロール終わるとこんな感じでファイルができる

all_html.txt1 all_html.txt11 all_html.txt21 all_html.txt31 all_html.txt41

なので

% cat all_html.txt1 all_html.txt11 all_html.txt21 all_html.txt31 all_html.txt41 > all_html.txt

catで結合。シェルスクリプト超便利。プログラミングする必要一切なし!
そうするとこういうサイズ感のファイルができる。割とデカい。

% wc all_html.txt
700 107633 3662484 all_html.txt

あとURLを抜き出すだけなんだけどそのためにだらだらプログラミングするのもダルい。
rubularを使う。rubular超便利。これ無しで正規表現書く自信無いw
http://rubular.com/
で、こんな正規表現の出来上がり。

<a href=\"\/\d{14}\">

普通ならhoge.rbってファイル作ってこの正規表現で抜き出すスクリプト書くところだけど、2,3行で書けるのでrubyのREPLことirbを使う。最近irbで書くようになってから、対話型最高と思うようになった。わざわざファイル保存してターミナルで実行して、みたいな手間がないのが最高。あとこれ使うとワンライナーが上手くなりがち。
出来たスクリプトがこれ

urls = []
File.read("./all_html.txt").gsub(/<a href=\"\/(\d{14})\">/){|str|urls << $~.to_a[1]}

試しにURLの数を数えてみる。

irb(main):003:0> urls.count
=> 4142

絶対かぶってる
なので、uniqしましょう。わざわざ代入もだるいのでレシーバ自身をuniq!

irb(main):004:0> urls.uniq!
irb(main):005:0> urls.count
=> 1365

4倍近くもかぶってたww
普通ならかぶらないようクローリング時点で制御するような書き方するんだろうけど、
最近は集められるだけ集めて後から削ったり編集したりする書き方のほうがセンスいいと思うようになってきた。関数型のパラダイムかもしれん

今回引数+9で書いて並列数5で動かしたけど(はてなサーバーに負荷がかかってしまうので)+99くらいまでなら余裕で動かせそう。+999は分からん。+9999はやってみたすぎるけど、たぶん、1ページ5秒計算で約830分だから14時間くらい。たしか増田は30万ページくらいあったから、並列の数を30にして(なんらかのサーバーサイドのサービス使ったほうがよさげ)14時間で全部のhtml抜ける(かもしれない)。がしかし、urlを抜き出すスクリプトをかなり最適化しないと普通のマシンスペックだと24時間以上かかるかもしれない(今回もクローリングよりパーズに時間かかったから並列化に対応したparser書く必要ある)。
当然だけど、コードの使用は自己責任で。BAN食らっても知らん。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2