Gå til innhold

Anbefalte innlegg

Hei!

Noen som har kjennskap til lxml i python?

Prøver å få samlet noe informasjon fra nettet, men sitter litt fast.

Jeg lagde nettopp ett eksempel dokument (HTML koden under) for å forklare situasjonen.

 

Jeg ønsker å få tak i teksen fra <title>, <color> & <day> under hver "box" classe. Vil ikke ha <food>

<div class="main">
	<div class="box">
		<title>Title1</title>
		<color>Red</color>
		<day>Monday</day>
		<food>Bacon</food>
	</div>
	<div class="box">
		<title>Title2</title>
		<color>Yellow</color>
		<food>Onion</food>
	</div>
	<div class="box">
		<title>Title3</title>
		<color>Green</color>
		<day>Friday</day>
		<food>Sausage</food>
	</div>
</div>

Jeg gjør dette ved å bruke XPATH fra lxml.html slik:

from lxml import html

url = "http://www.blabla.com"
tree = html.parse(url)

title = tree.xpath(//div[@class="box"]/title)
color = tree.xpath(//div[@class="box"]/color)
day = tree.xpath(//div[@class="box"]/day)

Her blir alle funn(Element span) av <title> lagret i en liste som heter title. Teksen henter jeg ut ved å skrive:

for i in title:
	print i.text.strip()

Systemet her går da ut på at alle titlene blir lagret i title, alle fargene blir lagret i color, osv.

 

Jeg vil nå ut i fra dette samle første element i hver liste sammen, slik at jeg kan gruppere dem i en ny liste. Vanskelig forklart, men jeg kan altså bruke
zip(title, color, day), for å gjøre jobben jeg vil.

 

Problemet oppstår når enkelte av classene ikke inneholder en <day>-tag. Da vil alt forskyve seg, og jeg vil ende opp med at zip-listen nr2 inneholder
<day>Friday</day>, mens zip-listen nr3 ikke vil inneholde en <day>-tag i det hele tatt.

 

Eksempel for forklaring ovenfor:

>>> title
['Title1', 'Title2', 'Title3']
>>> color
['Red', 'Yellow', 'Green']
>>> day
['Monday', 'Friday']
>>>
>>> zip(title, color, day)
['Title1', 'Red', 'Monday']    # OK
['Title2', 'Yellow', 'Friday'] # Her hører ikke friday til.
['Title3', 'Green']	       # Blir ikke generert av zip.

Derfor blir denne metoden av XPATH vanskelig å bruke til dette tilfellet, da den samler alt av en ting samtidig.

Det jeg ønsker er å f.eks loope gjennom denne teksten, og for hver gang jeg møter på class="box", så skal jeg først lete etter én title, én color & én day. Finner den ingen day, kan jeg enkelte bruke lst.append('<tom>'), for å tilpasse det samme system.

 

Men hvordan kan jeg gjøre dette?

Beklager dårlig forklaring...

 

Ønsket resultat:

loop(each encounter of class="box"):
	#find one title
	title.append(x)
	#find one color
	color.append(x)
	#find one day
	if day not found:
		day.append('<tom>')
	else:
		day.append(x)
#------
>>> title
['Title1', 'Title2', 'Title3']
>>> color
['Red', 'Yellow', 'Green']
>>> day
['Monday', '<tom>', 'Friday']
>>>
>>> zip(title, color, day)
['Title1', 'Red', 'Monday']
['Title2', 'Yellow', '<tom>']
['Title3', 'Green', 'Friday']

Takk på forhånd.

Endret av Axxxy
Lenke til kommentar
Videoannonse
Annonse

Viss struktur alltid er den samme,insert <tom> i midten,viss lengden av listen er mindre enn 3.

>>> day = tree.xpath('//div[@class="box"]/day/text()')
>>> day
['Monday', 'Friday']
>>> if len(day) < 3:
...     day.insert(1, '<tom>')
...     
>>> day
['Monday', '<tom>', 'Friday']


>>> zip(title, color, day)
[('Title1', 'Red', 'Monday'),
 ('Title2', 'Yellow', '<tom>'),
 ('Title3', 'Green', 'Friday')]

 

Lenke til kommentar

Strukturen er ikke alltid den samme. Sidene inneholder mangfoldige box classer, og <day> er som oftest plassert tilfeldig.

Heldigvis fant jeg løsningen etter mange timers lesning og testing.

 

I XPATH vil "//" representere absolute path, som vil si at den entene hever en error, eller søker i fra root elementet, selv når jeg prøver å søke via class="box" elementet. Løsningen var lett, bytt ut "//" med ".//".

Ett punktum først, vil representere local path.

 

Utestet kode til dette eksempelet:

tree = html.parse(url)

title, color, day = [], [], []
for elem in tree.xpath('//div[@class="box"]'):
	curTitle = elem.xpath('.//title/text()')
	curColor = elem.xpath('.//color/text()')
	curDay = elem.xpath('.//day/text()')
	if curTitle:
		title.append(curTitle[0].strip())
	else:
		title.append('<tom>')
	if curColor:
		color.append(curColor[0].strip())
	else:
		color.append('<tom>')
	if curDay:
		day.append(curDay[0].strip())
	else:
		day.append('<tom>')

>>> print (len(title), len(color), len(day))
(3, 3, 3)
Lenke til kommentar

Hei!

Noen som har kjennskap til lxml i python? (...)

Med forbehold om misforståelse av problemstillingen:

 

from lxml import html
from StringIO import StringIO as IO
from collections import namedtuple

tree = html.parse(IO(xml))
xmlobj = namedtuple("xmlobj", ["title", "color", "day"])

def extract_info(element):
    return xmlobj(title=element.findtext("title", ""),
                  color=element.findtext("color", ""),
                  day=element.findtext("day", ""))
# end extract_info

for node in tree.iterfind("//div[@class='box']"):
    print extract_info(node)
... og kjøring med eksempelet ditt gir:

 

xmlobj(title='Title1', color='Red', day='Monday')

xmlobj(title='Title2', color='Yellow', day='')

xmlobj(title='Title3', color='Green', day='Friday')

 

Var det det du ville?

 

edit: trykkfeil. Også, det å bygge ut lister av dager/titler/farger er nokså trivielt, gitt en liste ut av iterfind().

Endret av zotbar1234
Lenke til kommentar

... og kjøring med eksempelet ditt gir:

xmlobj(title='Title1', color='Red', day='Monday')

xmlobj(title='Title2', color='Yellow', day='')

xmlobj(title='Title3', color='Green', day='Friday')

 

Var det det du ville?

 

edit: trykkfeil. Også, det å bygge ut lister av dager/titler/farger er nokså trivielt, gitt en liste ut av iterfind().

 

Resultatet blir ganske det samme. Husker ikke helt hva jeg gjorde når jeg testet. Prøvde iterfind(), iter(), finall(), find(), og noe mer, men endte konstant opp med error eller None som svar. Ser nå at jeg prøvde å bruke absolute path, uten å vite at den søkte fra root elementet.

 

Man oppnår det samme i ditt eksempel som her:

tree = *chunk of html*

for i in tree.xpath("//div[@class='box']"):
	# Finner tekst
	print i.findtext('title')
	# Finner kun første møtet med n i elementet
	print i.find('title').text
	# Finner alle møter med n i elementet
	print i.findall('title').text 

Jeg mente å ha lest flere plasser at xpath var det kjappeste. Kjørte nettopp en timeit test av xpath og findall(). findall() er dobbelt så rask.

tree = *chunk of html*
def func1():
	x = tree.xpath("//div[@class='box']")

def func2():
	x = tree.findall("div[@class='box']")

>>> timeit.timeit(func1, number=10000)
0.3098855718972793		#Best av 5
>>> timeit.timeit(func2, number=10000)
0.12920381991552077		#Best av 5

EDIT: Prøvde nettopp samme testen bare på en skikkelig html fil på rundt 1300 linjer

>>> timeit.timeit(func1, number=10000) #xpath()
3.2193610692207812		#Best av 5
>>> timeit.timeit(func2, number=10000) #findall()
0.07786131816033048		#Best av 5
Endret av Axxxy
Lenke til kommentar
Sjekk ut BeautifulSoup ... Da blir slike oppgaver trivielle

 

Spiller ingen rolle problemstilling blir lik,da både lxml og BeautifulSoup parser dette lett.

lxml er kraftigere(flere verktøy) enn BeautifulSoup,og har til og med BeautifulSoup bygd inn.

Når det er sakt så har BeautifulSoup vært en av mine favoritter i mange år.

 

Finne alle class='box' i BeautifulSoup soup.find_all('div', {'class': 'box'})

Så blir det samme jobben som i lxml og kjøre en løkke og ta ut ["title", "color", "day"].

Endret av snippsat
Lenke til kommentar

Opprett en konto eller logg inn for å kommentere

Du må være et medlem for å kunne skrive en kommentar

Opprett konto

Det er enkelt å melde seg inn for å starte en ny konto!

Start en konto

Logg inn

Har du allerede en konto? Logg inn her.

Logg inn nå
×
×
  • Opprett ny...