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

时间:2013-10-17 18:02:17

标签: python web-scraping scrapy

我正试图通过here来消除赔率。

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

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)

这仅记录条目:此市场目前无法使用.... 不是包含赔率的其他元素。

我尝试了几个没有运气的不同选择器。看起来,一旦我尝试进入元素div[@class="fb_day_type_wrapper"],我什么也得不到。使用scrapy shell我有相同的结果。

1 个答案:

答案 0 :(得分:6)

该网站使用javascript生成数据表。有一些替代方法,如scrapyjssplash,可以获取js呈现的html页面。如果您只需要刮一页,最好使用Selenium。

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

首先,启动scrapy shell,以便我们可以浏览网页:

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',

我们在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: '>]

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

[{names: {en: 'Newcastle v Liverpool'}, ...

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

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代码(一个字典)。我们可以使用json.loads但是为了这样做,js代码必须是一个有效的json对象,即在""中包含字段名和字符串。

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

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("'", '"')

这可能并不总是适用于所有情况,因为javascript代码的模式可能不容易被单个re.sub和/或.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字段从其中的三个ev_id构建一个data[0]。我将假设data[1]data[2]具有直接关联,In [12]: map(lambda v: v['ev_id'], data[2]) Out [12]: [5889932, 5889932, 5889932, 5889933, 5889933, 5889933, ... 每个事件包含3个项目。这可以通过以下方式轻松验证:

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

使用一些python-fu,我们可以合并记录:

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'},
 ...

最后,我们收集数据:

log.start

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

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

最后的评论:

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