xpath/python search then grab child nodes? - python

I am working on a scraper using python and selenium and I have an issue traversing xpath. I feel like this should be simple, but I'm obviously missing something.
I am able to navigate the site I am browsing fine, but I need to grab some SPAN text based on an XPATH search.
I am able to click the appropriate radio button(in this case the 1st one)
(driver.find_elements_by_name("start-date"))[0].click()
But I also need to capture the text next to the radio button which is captures in the span tags.
<label>
<input type="radio" name="start-date" value="1" data-start-date="/Date(1507854300000)/" data-end-date="/Date(1508200200000)/" group="15" type-id="8">
<span class="start-date">
10/12/2017<br>Summary text
</span>
</label>
In the above example, I'm looking to capture "10/12/2017" and "Summary text" into 2 string variables based on the find_elements_by_name search I used to find the radio button.
I then have a second, similar, collection issue, where I need to capture the span tags after searching by class name. This finds the appropriate parent node on the page:
(driver.find_element_by_xpath("//div[#class=\"MyClass\"]"))
Based on the node returned by that search, I want to grab "Text 1" and "Text 2" from the span tags below it.
<div class="MyClass">
<span>
<span>Text 1</span>
</span>
<span class="bullet">
</span>
<span>
<span>Text 2</span>
</span>
</div>
I am new to xpath, but from what I can gather, the span nodes I am looking for should be children of the nodes I found with my searches, and I should be able to traverse down the hierarchy somehow to get the values, I'm just not sure how.

It's actually very simple, all WebElement objects have the same find_element_by_* methods that the WebDriver object has, with the main difference that the element methods change the context to that element, meaning that it will only have children of the selected element.
With that in mind you should be able to do:
my_element = driver.find_element_by_class_name('MyClass')
my_spans = my_element.find_elements_by_css_selector('span>span')
What happens here is that we grab the first element with class MyClass, then from the context of that element we search for elements that are span AND children of a span

you can try with the following x-path.
//div[#class='MyClass']/span[1]/span ---- To get Text 1
//div[#class='MyClass']/span[3]/span -----To get Text 2
or
(//div[#class='MyClass']/span/span)[1] ---- To get Text 1
(//div[#class='MyClass']/span/span)[2] ---- To get Text 2

Related

Python: XPATH searching within a node

What is a good way to select multiple nodes from with in a node in a html code using xpath?
I have this code (actually this repeated 23 times);
<li>
<a class="Title" href="http://www.google.com" >Google</a>
<div class="Info">
<p>
text
</p>
<p class="Date">Status: Under development</p>
</div>
I am trying to get both Title and Date and have two different XPATH querys like this;
//a[#class="Title"]/#href
//p[#class="Date"]
But when I do this I get two returns with 23 and 22 values each. This is because at one point in the HTML code Date is not present. Therefore I would like to stay inside the li and search for Title and Date within that li so I can check if there are any values.
I changed my XPATH to this;
//li
In my return Element I can see that there are two sub elements, a and div but I cannot seem to figure out how I am supposed to handle what is inside the return Element?
When you want to search elements within the current node you need to start your Xpath pattern with a dot.
For example:
.//a[#class="Title"]/#href
.//p[#class="Date"]

How to select a radio button followed by text with XPath?

I have a radio button with value as HTML as follows:
<div class='result'>
<span>
<input type='radio'/>
option1
</span>
<span>
<input type='radio'/>
option2
</span>
<span>
<input type='radio'/>
option3
</span>
</div>
I tried the following XPath, but this isn't working:
//span[contains(text(),'option1')]/input[#type='radio']
Please help me write XPath for this.
There are actually two text nodes in target span: the first one is just an empty string before <input> and the second- after <input> (the one that contains "option1")
And your XPath //span[contains(text(),'option1')] means return span that contains "option1" in first text node.
You can use one of below expressions to match required input:
//span[normalize-space()="option1"]/input[#type="radio"]
//span[contains(text()[2],'option1')]/input[#type='radio']
There are two text elements per span. One precedes the input element, and one follows it, but the first one is essentially empty.
In this code I find the input elements, then their parents, then the second text elements of those span parents.
>>> from scrapy.selector import Selector
>>> selector = Selector(text=open('temp.html').read())
>>> for item in selector.xpath('.//input[#type="radio"]/../text()[2]'):
... item.extract()
...
'\noption1\n'
'\noption2\n'
'\noption3\n'
try this to select option 1
//input[#type='radio']/preceding::span[1][contains(.,'option1')]
I guess you can't use text() here. Because this function returns a sequence of child text nodes of current span element. There are 2 text nodes in your example:
<span>
<input type='radio'/>
option1
</span>
1st text node is between <span> and <input type='radio'/> containing just a newline.
2nd text node is between <input type='radio'/> and </span> containing option1 text plus 2 newlines (at the begining and at the ending).
contains function expects a string argument instead of a sequence. I think it will take only first text node from the sequence, which contains just a newline.
If you need to select input followed by some text node you can use the following expression:
//input[#type='radio'][contains(following-sibling::text(), 'option1')]
If you need to select span containing text option1 and input with #type='radio', you can try the following expression:
//span[contains(., 'option1') and input/#type='radio']
If you need to select input instead of span then use the following expression:
//span[contains(., 'option1')]/input[#type='radio']
I can suggest you the following resources to gain some information about XPath. W3C recomendations contains a full description of XPath. If you use XPath 2.0 then you can look at:
XML Path Language (XPath) 2.0
XQuery 1.0 and XPath 2.0 Functions and Operators
For XPath 3.0 look at:
XML Path Language (XPath) 3.0
XPath and XQuery Functions and Operators 3
These recomendations are big enough and hard to read. But you can find in these documents a list of all available axes including following-sibling::, a description of text(), a description of contains(), etc.
Also there are a lot of brief XPath tutorials. For example you can look at this one.

Returning a list of values from a list of dictionaries where keys equal a certain value

<div id="tabs" class="clearfix">
<ul id="remove">
<li class="btn_arrow_tab left inactive">
<a href="#" class="doubleText">Pay Monthly <small>View standard rates and Bolt Ons</small>
</a>
</li>
<li class="btn_arrow_tab right inactive">
<a href="#" class="doubleText">Pay & Go<small>View standard rates and Bolt Ons</small>
</a>
</li>
</ul>
</div>
I have no experience in webscraping and trying to follow example and the docs to click on the button with text 'Pay Monthly'. This button then dynamically displays some text which I need to copy. How do I go about clicking this for starters, and then reading the text which is displayed. I am trying it with Selenium, would beautifulsoup be better? I have been trying this line of code but it isn't doing anything:
driver.find_element_by_xpath("//a[text()[contains(.,'Pay Monthly')]]").click()
It is always good practice to use mixture of absolute and relative xpath to locate a element.
First thing you should find is a parent that has a unique identifier. The element you mentioned has two parent items with a static id. One is root div and another is ul.
Now either we can follow your path and find the element using Text. Any of the following shall work.
driver.find_element_by_xpath("//div[#id='tabs']//a[text()[contains(.,'Pay Monthly')]]").click()
driver.find_element_by_xpath("//ul[#id='remove']//a[text()[contains(.,'Pay Monthly')]]").click()
But, if the item is static element and considering your goal here, I would suggest the following method. indexing your xpath when it returns multiple elements.
myElement = driver.find_element_by_xpath("//div[#id='tabs']//a[#href='#'][1]")
myElement.click()
And then you can capture the text. You can put some wait to ensure the text gets changed.
myText = myElement.text
Let me know if this doesn't work.

How to extract text from HTML (after certain string)

I have the following HTML:
<li class="group-ib medium-gap line-120 vertical-offset-10">
<i class="fa fa-angle-right font-bold font-95 text-primary text-dark">
::before
</i>
<span>
abc:
<b class="text-primary text-dark">st1</b>
</span>
</li>
And I want to extract str1 which always happens after abc. I was able to do it by using the XPATH link:
xpath('.//b[#class = "text-primary text-dark"]')[0].text
But the solution depended on it being the first appearance of this particular class, which appears more than once and isn't always in the same order. I was wondering if there was a way to search the HTML for abc and pull the subsequent text?
Maybe find the element that contains abc, navigate to child/parent if needed, get text.
Example of selectors:
Find any(* is for any tag) element that contains abc text and select any child.
//*[contains(text(), 'abc')]/*
Find any(* is for any tag) element that contains abc text and select his b child.
//*[contains(text(), 'abc')]/b
Find li element that has an element which contains text abc and select b element from inside it (inside li), use // since b is not first child of li.
//li[.//[contains(text(), 'abc')]]//b
If you know abc then start from there, see what element is returned and if needed to navigate to parent/ancestor/child.
For more about xpath please see w3schools xpath selectors
The following xpath should give the text you are searching for
//*[contains(text(),'abc')]/*[#class='text-primary text-dark'][1]/text()
assuming the str1 you are looking for should always be under elements with attribute class=text-primary text-dark
also assuming that you want to get the first such occurrence ( ignore the other text-primary text-darks )- that is why [1]
This xpath ensures that the node you are searching for those classes have a text abc before searching them.

How to find element on page with text which is dynamically changed using web driver?

I'm looking for way to find element which contain some exactly text, the problem is this text dynamically changes every time.
It looks like this:
<div class="some class" ng-class="{ 'ngSorted': !col.noSortVisible90 }">
<span ng-call-text class="ngbinding" style="cursor: defaulte;">some text and digits</span>
Where "some text and digits" element that I need.
Could somebody help me with this?
UPD: I have a lot elements with the same classes on page and also I know text phrase thet should be fount, I can provide this text to my code as parameter.
You can use the id attribute
<span ng-call-text id="snarfblat" class="ngbinding" style="cursor: defaulte;">some text and digits</span>
so you can access it within JavaScript with
document.getElementById("snarfblat");
Why don't you use Xpath or CSSSelector to reach to your target element, maybe on of its parent has a unique Id or a property, start from there and reach you destination i.e the concerned HTML tag with dynamic text

Categories

Resources