このサイトでは、javascript を使用してデータ テーブルを生成しています。js でレンダリングされた html ページを取得できる、scrapyjsやスプラッシュなどの代替手段がいくつかあります。1 ページだけスクレイピングする必要がある場合は、Selenium を使用する方がよい場合があります。
それ以外の場合は、ハードコア モードに移行し、データを使用してサイトで何が起こっているかをリバース エンジニアリングする必要がある場合があります。その方法をお教えします。
まず、 を起動してscrapy shell
、Web ページを探索できるようにします。
scrapy shell http://www.paddypower.com/football/football-matches/premier-league
注:私はpython 2.7.4、ipython 0.13.2、およびscrapy 0.18.0を使用しています。
ブラウザで「Crystal Palace v Fulham」のソースを検索すると、その参照を持つ JavaScript コードがあることがわかります。<script>
ブロックは次のようになります。
document.bodyOnLoad.push(function() {
lb_fb_cpn_init(
"",
"html",
"MR_224",
{category: 'SOCCER',
この要素をシェルで検索します。
In [1]: hxs.select('//script[contains(., "lb_fb_cpn_init")]')
Out[1]: [<HtmlXPathSelector xpath='//script[contains(., "lb_fb_cpn_init")]' data=u'<script type="text/javascript">\n/* $Id: '>]
引数をlb_fb_cpn_init
調べると、探しているデータが次の形式で引数として渡されていることがわかります。
[{names: {en: 'Newcastle v Liverpool'}, ...
実際には、そのような 3 つの引数があります。
In [2]: hxs.select('//script[contains(., "lb_fb_cpn_init")]').re('\[{names:')
Out[2]: [u'[{names:', u'[{names:', u'[{names:']
したがって、それらすべてを抽出します。多くの正規表現を使用していることに注意してください。
In [3]: js_args = hxs.select('//script[contains(., "lb_fb_cpn_init")]').re(r'(\[{names:(?:.+?)\]),')
In [4]: len(js_args)
Out[4]: 3
ここでの考え方は、javascript コード (リテラル オブジェクト) を python コード (dict) に解析したいということです。ただし、そのためにはjson.loads
、js コードは有効な json オブジェクトである必要があります。つまり、フィールド名と文字列が で囲まれている必要があります""
。
そうしていきます。最初に、JavaScript リストとして引数を 1 つの文字列に結合します。
In [5]: args_raw = '[{}]'.format(', '.join(js_args))
次に、フィールド名を囲み、一重引用符を二重引用符""
で置き換えます。
In [6]: import re
In [7]: args_json = re.sub(r'(,\s?|{)(\w+):', r'\1"\2":', args_raw).replace("'", '"')
re.sub
JavaScript コードには単一のand/orで置き換えるのが簡単ではないパターンが含まれている可能性があるため、これはすべてのケースで常に機能するとは限りません.replace
。
JavaScript コードを json オブジェクトとして解析する準備が整いました。
In [8]: import json
In [9]: data = json.loads(args_json)
In [10]: len(data)
Out[10]: 3
ここでは、イベント名とオッズだけを探しています。data
コンテンツを見て、それがどのように見えるかを確認できます。
幸いなことに、データには相関関係があるようです。
In [11]: map(len, data)
Out[11]: [20, 20, 60]
dict
フィールドを使用して、3 つから1 つを作成することもできev_id
ます。直接的な相関関係があり、イベントごとに 3 つのアイテムが含まれているdata[0]
と仮定します。これは、次の方法で簡単に確認できます。data[1]
data[2]
In [12]: map(lambda v: v['ev_id'], data[2])
Out [12]:
[5889932,
5889932,
5889932,
5889933,
5889933,
5889933,
...
いくつかの python-fu を使用して、レコードをマージできます。
In [13]: odds = iter(data[2])
In [14]: odds_merged = zip(odds, odds, odds)
In [15]: data_merged = zip(data[0], data[1], odds_merged)
In [16]: len(data_merged)
Out[16]: 20
最後に、データを収集します。
In [17]: get_odd = lambda obj: (obj['names']['en'], '/'.join([obj['lp_num'], obj['lp_den']]))
In [18]: event_odds = []
In [19]: for event, _, odds in data_merged:
....: event_odds.append({'name': event['names']['en'], 'odds': dict(map(get_odd, odds)), 'url': event['url']})
....:
In [20]: event_odds
Out[20]:
[{'name': u'Newcastle v Liverpool',
'odds': {u'Draw': u'14/5', u'Liverpool': u'17/20', u'Newcastle': u'3/1'},
'url': u'http://www.paddypower.com/football/football-matches/premier-league-matches/Newcastle%2dv%2dLiverpool-5889932.html'},
{'name': u'Arsenal v Norwich',
'odds': {u'Arsenal': u'3/10', u'Draw': u'9/2', u'Norwich': u'9/1'},
'url': u'http://www.paddypower.com/football/football-matches/premier-league-matches/Arsenal%2dv%2dNorwich-5889933.html'},
{'name': u'Chelsea v Cardiff',
'odds': {u'Cardiff': u'10/1', u'Chelsea': u'1/4', u'Draw': u'5/1'},
'url': u'http://www.paddypower.com/football/football-matches/premier-league-matches/Chelsea%2dv%2dCardiff-5889934.html'},
{'name': u'Everton v Hull',
'odds': {u'Draw': u'10/3', u'Everton': u'4/9', u'Hull': u'13/2'},
'url': u'http://www.paddypower.com/football/football-matches/premier-league-matches/Everton%2dv%2dHull-5889935.html'},
{'name': u'Man Utd v Southampton',
'odds': {u'Draw': u'3/1', u'Man Utd': u'8/15', u'Southampton': u'11/2'},
'url': u'http://www.paddypower.com/football/football-matches/premier-league-matches/Man%2dUtd%2dv%2dSouthampton-5889939.html'},
...
ご覧のとおり、Web スクレイピングは非常に難しい (そして楽しい!) 場合があります。すべては、Web サイトがデータを表示する方法に依存します。ここでは、Selenium を使用するだけで時間を節約できますが、大規模な Web サイトをスクレイピングしようとしている場合、Selenium は Scrapy に比べて非常に遅くなります。
また、サイトのコードが頻繁に更新されるかどうかも考慮する必要があります。その場合、js コードのリバース エンジニアリングにより多くの時間を費やすことになります。その場合、scrapyjsやスプラッシュなどのソリューションがより適切なオプションになる可能性があります。
最後のコメント:
- これで、データの抽出に必要なすべてのコードが揃いました。これをスパイダー コールバックに統合し、アイテムをビルドする必要があります。
- 使用しないでください
log.start
。設定LOG_FILE
(コマンドライン引数: --set LOG_FILE=mylog.txt
) を使用します。
.extract()
常にリストを返すことを思い出してください。