Scrapy hxs.select()不选择所有结果

use*_*646 4 python scrapy web-scraping

我正试图从这里开始捣蛋.

目前只是尝试使用以下蜘蛛记录结果:

def parse(self, response):         
   log.start("LogFile.txt", log.DEBUG);

   hxs = HtmlXPathSelector(response)
   sites = hxs.select('//div[@class="fb_day_type_wrapper"]')

   items = []
   for site in sites:
       siteAddress = urlparse.urljoin(response.url, site.extract())
       self.log('Found category url: %s' % siteAddress)
Run Code Online (Sandbox Code Playgroud)

这仅记录条目:此市场目前不可用....不包含赔率的其他元素.

我试过几个不同的选择器没有运气.看起来,一旦我尝试进入元素内部,div[@class="fb_day_type_wrapper"]我什么都没有回来.我使用scrapy shell有相同的结果.

Rol*_*Max 6

该网站使用javascript生成数据表.有一些替代品,如scrapyjssplash,允许获取js渲染的html页面.如果您只需要刮一页,最好使用Selenium.

否则,您可能需要进入硬核模式并使用数据对站点中发生的情况进行逆向工程.我会告诉你如何做到这一点.

首先,开始,scrapy shell以便我们可以浏览网页:

scrapy shell http://www.paddypower.com/football/football-matches/premier-league
Run Code Online (Sandbox Code Playgroud)

注意:我使用的是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',
Run Code Online (Sandbox Code Playgroud)

我们在shell中查找此元素:

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: '>]
Run Code Online (Sandbox Code Playgroud)

如果查找lb_fb_cpn_init参数,您将看到我们要查找的数据作为参数传递:

[{names: {en: 'Newcastle v Liverpool'}, ...
Run Code Online (Sandbox Code Playgroud)

实际上有三个这样的论点:

In [2]: hxs.select('//script[contains(., "lb_fb_cpn_init")]').re('\[{names:')
Out[2]: [u'[{names:', u'[{names:', u'[{names:']
Run Code Online (Sandbox Code Playgroud)

所以我们提取所有这些,注意我们使用了很多正则表达式:

In [3]: js_args = hxs.select('//script[contains(., "lb_fb_cpn_init")]').re(r'(\[{names:(?:.+?)\]),')

In [4]: len(js_args)
Out[4]: 3
Run Code Online (Sandbox Code Playgroud)

这里的想法是我们想要将javascript代码(这是一个文字对象)解析为python代码(一个字典).我们可以使用json.loads但是要这样做,js代码必须是一个有效的json对象,也就是说,包含字段名称和字符串"".

我们继续这样做.首先,我将单个字符串中的参数作为javascript列表加入:

In [5]: args_raw = '[{}]'.format(', '.join(js_args))
Run Code Online (Sandbox Code Playgroud)

然后,我们附上的字段名成"" 单引号用双引号替换:

In [6]: import re

In [7]: args_json = re.sub(r'(,\s?|{)(\w+):', r'\1"\2":', args_raw).replace("'", '"')
Run Code Online (Sandbox Code Playgroud)

这可能并不总是适用于所有情况,因为javascript代码可能具有不容易用单个re.sub和/或替换的模式.replace.

我们准备将javascript代码解析为json对象:

In [8]: import json

In [9]: data = json.loads(args_json)

In [10]: len(data)
Out[10]: 3
Run Code Online (Sandbox Code Playgroud)

在这里,我只是在寻找活动名称和赔率.您可以查看data内容以查看其外观.

幸运的是,数据似乎有相关性:

In [11]: map(len, data)
Out[11]: [20, 20, 60]
Run Code Online (Sandbox Code Playgroud)

您也可以dict使用该ev_id字段从其中的三个构建单个.我将假设data[0]data[1]hava直接相关,data[2]每个事件包含3个项目.这可以通过以下方式轻松验证:

In [12]: map(lambda v: v['ev_id'], data[2])
Out [12]:
[5889932,
 5889932,
 5889932,
 5889933,
 5889933,
 5889933,
 ...
Run Code Online (Sandbox Code Playgroud)

有了一些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
Run Code Online (Sandbox Code Playgroud)

最后,我们收集数据:

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'},
 ...
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,网络抓取可能非常具有挑战性(而且很有趣!).这完全取决于网站如何显示数据.在这里你可以通过使用Selenium来节省时间,但是如果你想要刮掉一个大型网站,与Scrapy相比,Selenium会非常慢.

此外,您还必须考虑该网站是否会经常获得代码更新,在这种情况下,您将花费更多时间对js代码进行逆向工程.在这种情况下,scrapyjssplash等解决方案可能是更好的选择.

最后的评论:

  • 现在您拥有了提取数据所需的所有代码.您需要将其集成到您的蜘蛛回调中并构建您的项目.
  • 不要用log.start.使用设置LOG_FILE(命令行参数:) --set LOG_FILE=mylog.txt.
  • 记住,.extract()总是返回一个列表.