Iterating Through Table Rows in Selenium (Python)

47,868

Solution 1

If you want to go row by row using an xpath, you can use the following:

h  = """<table class="datadisplaytable">
<tr>
<td class="dddefault">16759</td>
<td class="dddefault">MATH</td>
<td class="dddefault">123</td>
<td class="dddefault">001</td>
<td class="dddefault">Calculus</td>
<td class="dddefault"></td>
<td class="dddead"></td>
<td class="dddead"></td>
</tr>
<tr>
<td class="dddefault">16449</td>
<td class="dddefault">PHY</td>
<td class="dddefault">456</td>
<td class="dddefault">002</td>
<td class="dddefault">Physics</td>
<td class="dddefault"></td>
<td class="dddead"></td>
<td class="dddead"></td>
</tr>
</table>"""

from lxml import html
xml = html.fromstring(h)
# gets the table
table =  xml.xpath("//table[@class='datadisplaytable']")[0]


# iterate over all the rows   
for row in table.xpath(".//tr"):
     # get the text from all the td's from each row
    print([td.text for td in row.xpath(".//td[@class='dddefault'][text()])

Which outputs:

['16759', 'MATH', '123', '001', 'Calculus']
['16449', 'PHY', '456', '002', 'Physics']

Using td[text()] will avoid getting any Nones returned for the td's that hold no text.

So to do the same using selenium you would:

table =  driver.find_element_by_xpath("//table[@class='datadisplaytable']")

for row in table.find_elements_by_xpath(".//tr"):
    print([td.text for td in row.find_elements_by_xpath(".//td[@class='dddefault'][1]"])

For multiple tables:

def get_row_data(table):
   for row in table.find_elements_by_xpath(".//tr"):
        yield [td.text for td in row.find_elements_by_xpath(".//td[@class='dddefault'][text()]"])


for table in driver.find_elements_by_xpath("//table[@class='datadisplaytable']"):
    for data in get_row_data(table):
        # use the data

Solution 2

XPath is fragile. It's better to use CSS selectors or classes:

mytable = find_element_by_css_selector('table.datadisplaytable')
for row in mytable.find_elements_by_css_selector('tr'):
    for cell in row.find_elements_by_tag_name('td'):
        print(cell.text)

Solution 3

Correction of the Selenium part of @Padraic Cunningham's answer:

table = driver.find_element_by_xpath("//table[@class='datadisplaytable']")

for row in table.find_elements_by_xpath(".//tr"):
    print([td.text for td in row.find_elements_by_xpath(".//td[@class='dddefault']")])

Note: there was one missing round bracket at the end; also removed the [1] index, to match the first XML example.

Another note: Though, the example with the index [1] should also be preserved, to show how to extract individual elements.

Solution 4

Another Version (modified and corrected post by Padraic Cunningham): Tested with Python 3.x

#!/usr/bin/python

h  = """<table class="datadisplaytable">
<tr>
<td class="dddefault">16759</td>
<td class="dddefault">MATH</td>
<td class="dddefault">123</td>
<td class="dddefault">001</td>
<td class="dddefault">Calculus</td>
<td class="dddefault"></td>
<td class="dddead"></td>
<td class="dddead"></td>
</tr>
<tr>
<td class="dddefault">16449</td>
<td class="dddefault">PHY</td>
<td class="dddefault">456</td>
<td class="dddefault">002</td>
<td class="dddefault">Physics</td>
<td class="dddefault"></td>
<td class="dddead"></td>
<td class="dddead"></td>
</tr>
</table>"""

from lxml import html
xml = html.fromstring(h)
# gets the table
table =  xml.xpath("//table[@class='datadisplaytable']")[0]


# iterate over all the rows   
for row in table.xpath(".//tr"):
     # get the text from all the td's from each row
    print([td.text for td in row.xpath(".//td[@class='dddefault']")])
Share:
47,868
Fiery Phoenix
Author by

Fiery Phoenix

Computer Science Student. Love gaming, reading, traveling, and being an occasional geek.

Updated on July 12, 2022

Comments

  • Fiery Phoenix
    Fiery Phoenix almost 2 years

    I have a webpage with a table that only appears when I click 'Inspect Element' and is not visible through the View Source page. The table contains only two rows with several cells each and looks similar to this:

    <table class="datadisplaytable">
    <tbody>
    <tr>
    <td class="dddefault">16759</td>
    <td class="dddefault">MATH</td>
    <td class="dddefault">123</td>
    <td class="dddefault">001</td>
    <td class="dddefault">Calculus</td>
    <td class="dddefault"></td>
    <td class="dddead"></td>
    <td class="dddead"></td>
    </tr>
    <tr>
    <td class="dddefault">16449</td>
    <td class="dddefault">PHY</td>
    <td class="dddefault">456</td>
    <td class="dddefault">002</td>
    <td class="dddefault">Physics</td>
    <td class="dddefault"></td>
    <td class="dddead"></td>
    <td class="dddead"></td>
    </tr>
    </tbody>
    </table>
    

    What I'm trying to do is to iterate through the rows and return the text contained in each cell. I can't really seem to do it with Selenium. The elements contain no IDs and I'm not sure how else to get them. I'm not very familiar with using xpaths and such.

    Here is a debugging attempt that returns a TypeError:

    def check_grades(self):
        table = []
        for i in self.driver.find_element_by_class_name("dddefault"):
            table.append(i)
        print(table)
    

    What is an easy way to get the text from the rows?

  • Fiery Phoenix
    Fiery Phoenix about 8 years
    Thank you. But, what if there is one more than one table with the same class name, i.e. datadisplaytable? My function always seems to go with the first table with that name, when I'm looking for the next one (which is the one below it).
  • Padraic Cunningham
    Padraic Cunningham about 8 years
    Then use tables = driver.find_elements_by_xpath("//table[@class='datadisplayta‌​ble']") and just apply the logic per table, ie for table in tables... If the data is populates using ajax you might be able to mimic the post, can you share the link?
  • Fiery Phoenix
    Fiery Phoenix about 8 years
    Unfortunately, the link requires a login and is not publicly available. However, I am in the process of testing this logic and will report back when I have an update. Thank you again.
  • Padraic Cunningham
    Padraic Cunningham about 8 years
    No worries, the logic should work fine once the html has fully rendered before you parse it.
  • Fiery Phoenix
    Fiery Phoenix about 8 years
    Works! Thanks a lot!
  • Mindaugas Bernatavičius
    Mindaugas Bernatavičius almost 5 years
    Depends on how you use it, for example: "//span[contains(text(), 'Some Text of Interest')]" is robust and conveys what element is being interacted with.