XPath generator

0

Witam,

mam problemy z prawidłowym pisaniem xpath dla plików xml.
Czy jest jakiś dobry generator xpath któremu pokaże jaki tag chce wyciągnąć a on mi wypiszę prawidłowy xpath ?

Pozdrawiam

3

w dev konsoli w Chromie masz opcje "copy as xpath"

0

W czym piszesz?

1

W zależności od elementu i jego budowy często zamiast dłubać pełną ścieżkę, często zastosować warunki aby lokalizować go na podstawie jego atrybutów lub zawartości

//*[@id="..."] // id równe ...
//*[@class="..."] // klasa równa ...
//*[span[@type="field"]] // zawiera span z atrybutem type równym "field"

Takie podejście jest generalnie bezpieczniejsze, jeżeli XML/HTML się zmienia (częsty przypadek na stronach internetowych) - jeżeli wejdzie na przykład ekstra div w jakimś miejscu to nagle cała ścieżka jest unieważniona. Stąd strategia by zaczynać od najbardziej "wewnętrznego" elementu, który można jakoś jednoznacznie zidentyfikować, zamiast pracowicie iść po całej ścieżce.

1
Spearhead napisał(a):

W zależności od elementu i jego budowy często zamiast dłubać pełną ścieżkę, często zastosować warunki aby lokalizować go na podstawie jego atrybutów lub zawartości

//*[@id="..."] // id równe ...
//*[@class="..."] // klasa równa ...
//*[span[@type="field"]] // zawiera span z atrybutem type równym "field"

Takie podejście jest generalnie bezpieczniejsze, jeżeli XML/HTML się zmienia (częsty przypadek na stronach internetowych) - jeżeli wejdzie na przykład ekstra div w jakimś miejscu to nagle cała ścieżka jest unieważniona. Stąd strategia by zaczynać od najbardziej "wewnętrznego" elementu, który można jakość jednoznacznie zidentyfikować, zamiast pracowicie iść po całej ścieżce.

Może występować też dokładnie odwrotna sytuacja. Dokładnie wczoraj kombinowałam ze stroną, na której elementy zawierały id-y, tyle tylko, że te id-y były za każdym razem inne i jedyną sensowną metodą było odwołanie na zasadzie piiąte dziecko trzeciego elementu w drugim elemencie itp. I dopiero FF z rozszerzeniem Firebug potrafił produkować takie pełne ścieżki.

0

Napotkałem pewien problem.
Jak wyszukać wartości tego elementu ?
Przy założeniu że pozycja State występuje od 0 do liczby 3 cyfrowej.
Czyli może być State_0 oraz State_555

/Subject/Apartment/Rematrix/State_1/ReaAlarm
0
Caporeira napisał(a):

Napotkałem pewien problem.
Jak wyszukać wartości tego elementu ?
Przy założeniu że pozycja State występuje od 0 do liczby 3 cyfrowej.
Czyli może być State_0 oraz State_555

/Subject/Apartment/Rematrix/State_1/ReaAlarm

Od czego jest zależny ten numer? Jest wiele elementów z numerami State czy to zawsze jest jeden element? Od czego zależy, który element State wybierasz?

0
null null napisał(a):
Caporeira napisał(a):

Napotkałem pewien problem.
Jak wyszukać wartości tego elementu ?
Przy założeniu że pozycja State występuje od 0 do liczby 3 cyfrowej.
Czyli może być State_0 oraz State_555

/Subject/Apartment/Rematrix/State_1/ReaAlarm

Od czego jest zależny ten numer? Jest wiele elementów z numerami State czy to zawsze jest jeden element? Od czego zależy, który element State wybierasz?

Jest ich wiele i chce wszystkie wybrać.

0

Znalazłbym jakiś wspólny selektor (moze byc CSS) i wpakował to do listy. Na przykład przez takie coś

driver.find_elements_by_class_name

Zwróć uwagę, że tam jest 'elements' a nie 'element'.

XPathem tez da się to ogarnąć, ale trzeba będzie używać dziwnych odwołań, a po co to komu ;)

0
>>> from scrapy.selector import Selector
>>> sel = Selector(text="""<div>
... <State_0>10</State_0>
... <State_999>11</State_999>
... </div>
... """)
>>> sel.xpath('//*[starts-with(name(.), "state_")]').getall()
[u'<state_0>10</state_0>', u'<state_999>11</state_999>']

EDIT wygląda na to, że aby pobrać faktycznie nazwy elementów to składnia //*[starts-with(name(.), "State_")]/name() działa tylko w XPath 2, a scrapy wspiera mi tylko XPath 1 i mogę wywołać funkcję name() tylko na jednym węźle

>>> sel.xpath('name(//*[starts-with(name(.), "state_")])').getall()
[u'state_0']

Więc pewnie bym to obchodził przepuszczając otrzymane selektory przez regex:

>>> sel.xpath('//*[starts-with(name(.), "state_")]').re('<(state_\d+)>')
[u'state_0', u'state_999']
0

Ja używam biblioteki lxml (pracuje z plikami xml a nie stroną www).
A może prościej by się dało jakoś pominąć State i przeskoczyć od razu do ReaAlarm ?
Albo też myślałem że dało by się to połączyć xpath z regex.

0
Caporeira napisał(a):

Ja używam biblioteki lxml (pracuje z plikami xml a nie stroną www).

Bez znaczenia, składnia taka sama.

A może prościej by się dało jakoś pominąć State i przeskoczyć od razu do ReaAlarm ?

No to //ReaAlarm. Już ci pisałem wcześniej, żeby zamiast całej ścieżki brać elementy od środka.

0

Witam,
mam kolejny problem z napisaniem poprawnego wyrażenia xpath.
Mianowicie, chce znaleźć wszystkie elementy które zawierają State_ od 0 do 400:

/Subject/Apartment/Rematrix/State_0/Text

Jedyne co udało mi się osiągnąć jest takie coś (że wszystkie elementy):

/Subject/Apartment/Rematrix/*/Text

No i w drugim kroku, chce wyświetlić te elementy dla konkretnego atrybutu:

/Apartment/Rematrix/@ShortName='BEF'/*/Text

Proszę Was o radę, wskazówki jak to wykonać/ jak powinno wyglądać poprawny xpath :)

0

Czy masz jakiś wpływ na tą stronę? Możesz dodawać do elementów własne atrybuty, czy po prostu działasz na cudzej?
Czy te elementy mają już jakieś atrybuty?
Prawdę mówiąc wątpię, czy sama nazwa tagu w ścieżkach może zawierać symbole wieloznaczne wyjąwszy właśnie * dla każdego elementu.

0

Pracuje na lokalnych plikach XML.
Tak, te elementy mają już atrybuty.

Może połączenie xpath + regex by było rozwiązaniem ?

0

A to nie powinno być /Apartment/Rematrix/@ShortName='BEF'/Text?

0

Na mój gust nie ma co nadużywać XPath-a, w którym wsparcie dla regexów jest raczej marne, lepiej pobrać wszystkie elementy a potem przefiltrować je Pythonem. Pobierasz wszystkie elementy state_*, pobierasz ich nazwy, wydłubywasz numer regexem lub globem, rzutujesz na int, sprawdzasz czy mieszczą się w przedziale i jak wszystko jest ok, przetwarzać dalej. W Scrapy:

from scrapy.selector import Selector
sel = Selector(text="""<div>
    <State_0>10</State_0>
    <State_350>10</State_350>
    <State_450>10</State_450>
</div>
""")
>>> for s in sel.xpath('//*[starts-with(name(.), "state_")]'):
...     match = re.match('state_(\d+)', s.xpath('name(.)').get(''))
...     if match:
...         nr = int(match.group(1))
...         if 0 <= nr <= 400:
...             print(s.get())
... 
<state_0>10</state_0>
<state_350>10</state_350>
0
tsz napisał(a):

A to nie powinno być /Apartment/Rematrix/@ShortName='BEF'/Text?

Spearhead napisał(a):

Na mój gust nie ma co nadużywać XPath-a, w którym wsparcie dla regexów jest raczej marne, lepiej pobrać wszystkie elementy a potem przefiltrować je Pythonem. Pobierasz wszystkie elementy state_*, pobierasz ich nazwy, wydłubywasz numer regexem lub globem, rzutujesz na int, sprawdzasz czy mieszczą się w przedziale i jak wszystko jest ok, przetwarzać dalej. W Scrapy:

from scrapy.selector import Selector
sel = Selector(text="""<div>
    <State_0>10</State_0>
    <State_350>10</State_350>
    <State_450>10</State_450>
</div>
""")
>>> for s in sel.xpath('//*[starts-with(name(.), "state_")]'):
...     match = re.match('state_(\d+)', s.xpath('name(.)').get(''))
...     if match:
...         nr = int(match.group(1))
...         if 0 <= nr <= 400:
...             print(s.get())
... 
<state_0>10</state_0>
<state_350>10</state_350>

Tak oto wygląda przykładowy XML.
Chodzi o to aby wyśwtlać wszystkie wartości Text, podając nazwę ShortName="BEF" lub <Name>BEF</Name>.
Wiem że można tutaj zastosować coś takiego, "//Apartment/Rematrix[1]/*/Text", ale chciałbym to zrobić po nazwie.

<?xml version="1.0" encoding="utf-16"?>
<Subject ShortName="exported project" MainVersion="001">
	<Apartment ShortName="reaction matrix list" Version="001">
		<Rematrix ShortName="BEF" TypeID="2">
			<State_0 NODE="embedded object" TYPE="1">
				<Text/>
				<Status>0</Status>
			</State_0>
			<State_1 NODE=" embedded object" TYPE="1">
				<Text>Text1</Text>
				<Status>129</Status>
			</State_1>
			<State_2 NODE=" embedded object" TYPE="1">
				<Text>Text2</Text>
				<Status>129</Status>
			</State_2>
			<State_3 NODE=" embedded object" TYPE="1">
				<Text>Text3</Text>
				<Status>129</Status>
			</State_3>
			<State_4 NODE=" embedded object" TYPE="1">
				<Text>Text4</Text>
				<Status>129</Status>
			</State_4>
			<Name>BEF</Name>
			<Description/>
			<Type>2</Type>
			<ExternalReference/>
			<SystemModelGroup/>
		</Rematrix>
		<Rematrix ShortName="ZMIENNA" TypeID="1">
			<State_0 NODE="embedded object" TYPE="1">
				<Text/>
				<Status>0</Status>
			</State_0>
			<State_1 NODE=" embedded object" TYPE="1">
				<Text>String1</Text>
				<Status>0</Status>
			</State_1>
			<State_2 NODE=" embedded object" TYPE="1">
				<Text>String2</Text>
				<Status>0</Status>
			</State_2>
			<Name>ZMIENNA</Name>
			<Description/>
			<Type>1</Type>
			<ExternalReference/>
			<SystemModelGroup/>
		</Rematrix>
	</Apartment>
</Subject>
0

Żeby wyciągnąć wszystkie węzły Text z węzłów zaczynajhących się na State dla atrybutu ShortName="BEF" w węźle Rematrix mozesz użyć takiego wyrażenia XPath:

/Subject/Apartment/Rematrix[contains(@ShortName,'BEF')]/*[starts-with(local-name(), 'State')]/Text

Szukając w węźle Name:

/Subject/Apartment/Rematrix[Name='BEF']/*[starts-with(local-name(), 'State')]/Text
0
Panczo napisał(a):

Żeby wyciągnąć wszystkie węzły Text z węzłów zaczynajhących się na State dla atrybutu ShortName="BEF" w węźle Rematrix mozesz użyć takiego wyrażenia XPath:

/Subject/Apartment/Rematrix[contains(@ShortName,'BEF')]/*[starts-with(local-name(), 'State')]/Text

Szukając w węźle Name:

/Subject/Apartment/Rematrix[Name='BEF']/*[starts-with(local-name(), 'State')]/Text

Wow, świetna robota, oba działają :)
Z tym że w tym pierwszym występuje taki problem iż, jeśli są węzły o podobnych atrybutach, np.:
BEF, BEF1, BEF_test
to wtedy przeszukuje we wszystkich tych węzłach. A tego chciałem uniknąć.

0

Faktycznie, dawno nie pisalem xpath'a...

musisz zmienić: contains(@ShortName,'BEF') na @ShortName = 'BEF'

Możesz też połączyć oba warunki w jedno wyrażenie:

/Subject/Apartment/Rematrix[@ShortName='BEF' or Name='BEF']/*[starts-with(local-name(), 'State')]/Text

1 użytkowników online, w tym zalogowanych: 0, gości: 1