1

我正在使用XPathwithScrapy从电影网站 BoxOfficeMojo.com 上抓取数据。

作为一个一般性问题:我想知道如何在一个Xpath字符串中选择一个父节点的某些子节点。

根据我从中抓取数据的电影网页,有时我需要的数据位于不同的子节点,例如是否有链接。我将浏览大约 14000 部电影,所以这个过程需要自动化。

以此为例。我需要演员、导演和制片人。

这是Xpath给导演的:注意: %s 对应于找到该信息的确定索引 - 在动作杰克逊示例director中找到 at[1]actorsat [2]

 //div[@class="mp_box_content"]/table/tr[%s]/td[2]/font/text()

但是,是否存在指向导演页面的链接,这将是Xpath

 //div[@class="mp_box_content"]/table/tr[%s]/td[2]/font/a/text()

演员有点棘手,因为<br>列出了后续演员,可能是 an 的孩子/a或 parent的孩子/font,所以:

//div[@class="mp_box_content"]/table/tr[%s]/td[2]/font//a/text()

获取几乎所有的演员(除了那些有font/br)。

现在,我相信这里的主要问题是有多个//div[@class="mp_box_content"]- 我拥有的所有东西都可以工作,除了我最终也从其他mp_box_content. 我还添加了许多try:,except:声明以获取所有内容(演员、导演、制片人,他们都有或没有与他们相关的链接)。例如,以下是我Scrapy的演员代码:

 actors = hxs.select('//div[@class="mp_box_content"]/table/tr[%s]/td[2]/font//a/text()' % (locActor,)).extract()
 try:
     second = hxs.select('//div[@class="mp_box_content"]/table/tr[%s]/td[2]/font/text()' % (locActor,)).extract()
     for n in second:
         actors.append(n)
 except:
     actors = hxs.select('//div[@class="mp_box_content"]/table/tr[%s]/td[2]/font/text()' % (locActor,)).extract()

这是试图掩盖以下事实:第一个演员可能没有与他/她相关联的链接,而随后的演员有,第一个演员可能有一个与他/她相关联的链接,但其余的可能没有。

感谢您花时间阅读本文以及任何帮助我查找/解决此问题的尝试!如果需要更多信息,请告诉我。

4

1 回答 1

3

我假设您只对文本内容感兴趣,而不是演员页面的链接等。

这是一个直接使用lxml.html(和一点)的命题lxml.etree

  • 首先,我建议您td[2]按 的文本内容选择单元格td[1],使用诸如.//tr[starts-with(td[1], "Director")]/td[2]说明“导演”或“导演”之类的表达方式

  • 其次,测试各种表达式有无<font>,有无<a>等,使代码难以阅读和维护,由于您只对文本内容感兴趣,您不妨使用string(.//tr[starts-with(td[1], "Actor")]/td[2])获取文本,或使用lxml.html.tostring(e, method="text", encoding=unicode)在选定的元素上

  • 对于多个名称的<br>问题,我的做法通常是修改lxml包含目标内容的树,以向<br>元素添加特殊格式字符'.text.tail,例如 a \n,具有lxml' 的iter()功能之一。这对其他 HTML 块元素很有用,<hr>例如。

你可能会更好地理解我对一些蜘蛛代码的意思:

from scrapy.spider import BaseSpider
from scrapy.selector import HtmlXPathSelector
import lxml.etree
import lxml.html

MARKER = "|"
def br2nl(tree):
    for element in tree:
        for elem in element.iter("br"):
            elem.text = MARKER

def extract_category_lines(tree):
    if tree is not None and len(tree):
        # modify the tree by adding a MARKER after <br> elements
        br2nl(tree)

        # use lxml's .tostring() to get a unicode string
        # and split lines on the marker we added above
        # so we get lists of actors, producers, directors...
        return lxml.html.tostring(
            tree[0], method="text", encoding=unicode).split(MARKER)

class BoxOfficeMojoSpider(BaseSpider):
    name = "boxofficemojo"
    start_urls = [
        "http://www.boxofficemojo.com/movies/?id=actionjackson.htm",
        "http://www.boxofficemojo.com/movies/?id=cloudatlas.htm",
    ]

    # locate 2nd cell by text content of first cell
    XPATH_CATEGORY_CELL = lxml.etree.XPath('.//tr[starts-with(td[1], $category)]/td[2]')
    def parse(self, response):
        root = lxml.html.fromstring(response.body)

        # locate the "The Players" table
        players = root.xpath('//div[@class="mp_box"][div[@class="mp_box_tab"]="The Players"]/div[@class="mp_box_content"]/table')

        # we have only one table in "players" so the for loop is not really necessary
        for players_table in players:

            directors_cells = self.XPATH_CATEGORY_CELL(players_table,
                category="Director")
            actors_cells = self.XPATH_CATEGORY_CELL(players_table,
                category="Actor")
            producers_cells = self.XPATH_CATEGORY_CELL(players_table,
                category="Producer")
            writers_cells = self.XPATH_CATEGORY_CELL(players_table,
                category="Producer")
            composers_cells = self.XPATH_CATEGORY_CELL(players_table,
                category="Composer")

            directors = extract_category_lines(directors_cells)
            actors = extract_category_lines(actors_cells)
            producers = extract_category_lines(producers_cells)
            writers = extract_category_lines(writers_cells)
            composers = extract_category_lines(composers_cells)

            print "Directors:", directors
            print "Actors:", actors
            print "Producers:", producers
            print "Writers:", writers
            print "Composers:", composers
            # here you should of course populate scrapy items

代码肯定可以简化,但我希望你能明白。

您当然可以做类似HtmlXPathSelector的事情(string()例如使用 XPath 函数),但无需修改树<br>(如何使用 hxs 做到这一点?)它仅适用于您的情况下的非多个名称:

>>> hxs.select('string(//div[@class="mp_box"][div[@class="mp_box_tab"]="The Players"]/div[@class="mp_box_content"]/table//tr[contains(td, "Director")]/td[2])').extract()
[u'Craig R. Baxley']
>>> hxs.select('string(//div[@class="mp_box"][div[@class="mp_box_tab"]="The Players"]/div[@class="mp_box_content"]/table//tr[contains(td, "Actor")]/td[2])').extract()
[u'Carl WeathersCraig T. NelsonSharon Stone']
于 2013-08-25T22:09:27.340 回答