2013-08-25 7 views
1

J'utilise XPath avec Scrapy pour extraire des données d'un site de film BoxOfficeMojo.com.XPath: Sélectionner certains nœuds enfants

En général, je me demande comment sélectionner certains nœuds enfants d'un nœud parent dans une chaîne Xpath. En fonction de la page Web de film à partir de laquelle j'écris des données, les données dont j'ai besoin se trouvent parfois sur différents nœuds enfants, par exemple s'il existe ou non un lien. Je vais parcourir environ 14 000 films, donc ce processus doit être automatisé.

En utilisant this comme exemple. J'aurai besoin d'acteur (s), de réalisateur (s) et de producteur (s).

C'est le Xpath au directeur: Remarque:% s correspond à un indice déterminé où cette information se trouve - dans l'action Jackson exemple director se trouve à [1] et actors à [2].

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

Cependant, aurait un lien existe une page sur le directeur, ce serait le Xpath:

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

Les acteurs sont un peu plus compliqué, car il <br> inclus pour les acteurs suivants énumérés, qui peut-être les enfants d'un /a ou les enfants du parent /font, donc:

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

obtient tous les plus tous les acteurs s (sauf ceux avec font/br).

Maintenant, le principal problème ici, je crois, est qu'il y a plusieurs //div[@class="mp_box_content"] - tout ce que j'ai fonctionne SAUF que je finis par obtenir des chiffres d'autres mp_box_content. J'ai également ajouté de nombreuses instructions try:, except: afin de tout obtenir (acteurs, réalisateurs, producteurs qui ont et n'ont pas de liens associés). Par exemple, ce qui suit est mon Scrapy code pour les acteurs:

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() 

Ceci est une tentative de couvrir les faits: le premier acteur peut ne pas avoir un lien associé avec lui/elle et les acteurs suivants font, le premier l'acteur peut avoir un lien associé à lui/elle mais le reste peut ne pas.

J'apprécie le temps que vous avez lu et toutes les tentatives pour m'aider à trouver/résoudre ce problème! S'il vous plaît laissez-moi savoir si d'autres informations sont nécessaires.

Répondre

3

Je suppose que vous êtes uniquement intéressé par le contenu textuel, pas les liens vers les pages des acteurs, etc.

Voici une proposition en utilisant lxml.html (et un peu de lxml.etree) directement

  • D'abord, je vous recommande de sélectionner td[2] cellules par le contenu du texte de td[1], avec des expressions comme .//tr[starts-with(td[1], "Director")]/td[2] pour tenir compte de « directeur » , ou "directeurs"

  • Deuxièmement, le test de diverses expressions avec ou sans <font>, avec ou sans <a> etc, rend le code difficile à lire et à maintenir, et puisque vous êtes intereste d seulement dans le contenu du texte, vous pourriez aussi bien utiliser string(.//tr[starts-with(td[1], "Actor")]/td[2]) pour obtenir le texte ou utiliser lxml.html.tostring(e, method="text", encoding=unicode) sur certains éléments

  • Et pour la question <br> pour plusieurs noms, comme je le fais est de modifier généralement l'arbre lxml contenant le contenu ciblé pour ajouter un caractère de formatage spécial à <br> éléments '.text ou .tail, par exemple un \n, avec l'une des iter() fonctions. Cela peut être utile sur d'autres éléments de bloc HTML, par exemple <hr>.

Vous pouvez mieux voir ce que je veux dire avec un code d'araignée:

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 

Le code peut être simplifié pour sûr, mais je l'espère, vous voyez l'idée.

Vous pouvez faire des choses similaires avec HtmlXPathSelector bien sûr (avec la fonction string() XPath par exemple), mais sans modifier l'arbre pour <br> (comment faire avec hxs?) Il ne fonctionne que pour les noms non multiples dans votre cas :

>>> 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'] 
+0

Wow! Merci beaucoup d'avoir pris le temps de répondre! Je suis curieux, et je vais mettre en œuvre ces choses rapidement pour voir ce qui se passe, si ces méthodes vont éliminer la question d'obtenir des informations d'autres '[@ class =" mp_box_content "]', car c'est l'un des principaux problèmes? – DMML

+0

Vous obtiendrez uniquement le contenu de la table "The Players", et non les autres div [@ @ class = "mp_box_content"] '. J'ai corrigé 'br2nl' avec' .text' au lieu de '.tail', sinon certaines lignes ont été écrasées. Je présente également une expression XPath de compilation, de sorte que vous pouvez passer un argument 'category' en tant que variable XPath, qui représente le texte de la première cellule de la ligne que vous voulez –

Questions connexes