Extracting table cell text contents with xpath in rows for consumption? - php

I have something along the following lines in terms of HTML. I would like to extract the various contents of the table cells, however I discovered that there are some embedded divs occasionally in the cells and perhaps other oddities that I'm not sure of yet:
<p align="center">
<img src="some_image.gif" alt="Some Title">
</p>
<TABLE WIDTH=500 BORDER=1 class=textwhite ALIGN=center CELLPADDING=0 CELLSPACING=0>
<TR>
<TD colspan=4 ALIGN=center><b>Title</b></TD>
</TR>
<TR>
<TD ALIGN=center>Title</TD>
<TD ALIGN=center>date</TD>
<TD ALIGN=center>value</TD>
<TD ALIGN=center>value</TD>
</TR><TR>
<TD ALIGN=center>Title2</TD>
<TD ALIGN=center></TD>
<TD ALIGN=center><div class=redtext>----</div></TD>
<TD> </TD>
</TR><TR>
<TD ALIGN=center>Title3</TD>
<TD ALIGN=center><div class=yellowtext>value</div></TD>
<TD ALIGN=center><div class=redtext>value</div></TD>
<TD ALIGN=center>value<SUP>6</SUP></TD>
</TR><TR>
<TD ALIGN=center>Title4</TD>
<TD ALIGN=center><div class=bluetext>value</div></TD>
<TD ALIGN=center><div class=redtext>value</div></TD>
<TD> </TD>
</TR></TABLE>
<blockquote>
<p class="textstyle">
Text.
</p>
</blockquote>
My first impulse was to extract ALL element texts and just programmatically slice it up. I would watch for Title1, Title2, etc. to know when a row starts and then if a "----" is found meaning no value, just skip this row and move on. However, I realized that there is probably a better way of handling this with xpath directly.
How could this be solved with xpath so as to essentially give each cell's final child text content vs having to walk into each div if it exists? Or is there a more xpath like way to approach this?
Obviously I'm attempting to have the most flexible solution that will not be brittle if other unexpected elements crop up, even though they are unlikely.

The provided text isn't well-formed XML document, therefore XPath isn't applicable.
If you correct and covert it to a well-formed xml document as the one below, an expression like this might be useful:
/*/TABLE//TD//text()
or even:
//TABLE//TD//text()
Here is a wellformed XML document, constructed from the provided HTML:
<html>
<p align="center">
<img src="some_image.gif" alt="Some Title"/>
</p>
<TABLE WIDTH="500" BORDER="1" class="textwhite" ALIGN="center" CELLPADDING="0" CELLSPACING="0">
<TR>
<TD colspan="4" ALIGN="center">
<b>Title</b>
</TD>
</TR>
<TR>
<TD ALIGN="center">Title</TD>
<TD ALIGN="center">date</TD>
<TD ALIGN="center">value</TD>
<TD ALIGN="center">value</TD>
</TR>
<TR>
<TD ALIGN="center">Title2</TD>
<TD ALIGN="center"></TD>
<TD ALIGN="center">
<div class="redtext">----</div>
</TD>
<TD> </TD>
</TR>
<TR>
<TD ALIGN="center">Title3</TD>
<TD ALIGN="center">
<div class="yellowtext">value</div>
</TD>
<TD ALIGN="center">
<div class="redtext">value</div>
</TD>
<TD ALIGN="center">value
<SUP>6</SUP>
</TD>
</TR>
<TR>
<TD ALIGN="center">Title4</TD>
<TD ALIGN="center">
<div class="bluetext">value</div>
</TD>
<TD ALIGN="center">
<div class="redtext">value</div>
</TD>
<TD> </TD>
</TR>
</TABLE>
<blockquote>
<p class="textstyle"> Text. </p>
</blockquote>
</html>

So maybe you don't want to walk the divs, but here is my solution using lxml, which I highly recommend:
import re
from cStringIO import StringIO
from lxml import etree
def getTable(html, table_xpath, rows_xpath, cells_xpath):
"""Get a table on a webpage"""
parser = etree.HTMLParser()
# Build document tree and get table
root = etree.parse(StringIO(html), parser)
table = root.find(table_xpath)
if table == None:
print 'No table.'
return []
rows = table.findall(rows_xpath)
document = []
def cleanText(text):
"""Clean up text by replacing line breaks and tabs. """
return re.sub(r'[\r\n\t]+','',str(text).strip())
# iterate over the table rows and collect text from each cell.
for r in rows:
cells = r.findall(cells_xpath)
rowdata = []
for c in cells:
text = ''
it = c.itertext()
for i in it:
text += cleanText(i) + ' '
rowdata.append(text)
document.append(rowdata)
return document
html = """
<html><head><title></title></head><body>
<p align="center">
<img src="some_image.gif" alt="Some Title">
</p>
<TABLE WIDTH=500 BORDER=1 class=textwhite ALIGN=center CELLPADDING=0 CELLSPACING=0>
<TR>
<TD colspan=4 ALIGN=center><b>Title</b></TD>
</TR>
<TR>
<TD ALIGN=center>Title</TD>
<TD ALIGN=center>date</TD>
<TD ALIGN=center>value</TD>
<TD ALIGN=center>value</TD>
</TR><TR>
<TD ALIGN=center>Title2</TD>
<TD ALIGN=center></TD>
<TD ALIGN=center><div class=redtext>----</div></TD>
<TD> </TD>
</TR><TR>
<TD ALIGN=center>Title3</TD>
<TD ALIGN=center><div class=yellowtext>value</div></TD>
<TD ALIGN=center><div class=redtext>value</div></TD>
<TD ALIGN=center>value<SUP>6</SUP></TD>
</TR><TR>
<TD ALIGN=center>Title4</TD>
<TD ALIGN=center><div class=bluetext>value</div></TD>
<TD ALIGN=center><div class=redtext>value</div></TD>
<TD> </TD>
</TR></TABLE>
</body>
</html>
"""
tp = "//table[#width='500']"
rt = "tr"
cp = "td[#align='center']"
doc = getTable(html, tp, rt, cp)
print repr(doc)

I believe that your program is going to run into many problems as the input data is manipulated -- what if the case of 'title' changes, or there is a typo?
It's not really possible to make a rigorous solution to scraping someone else's website, as they can at no notice completely change everything. Better is normally to write tolerant and flexible code that at least tries to verify that its output is sane. In this case it's probably best to iterate over the results of '//table/tr', then inside this loop, process the td elements:
import lxml.etree
tree = lxml.etree.fromstring("<table><tr><td>test</td></tr><tr><td><div>test2</div></td></tr></table>")
stringify = lambda x : "".join(x.xpath(".//text()"))
for x in tree.xpath("//table/tr"):
print "New row"
for y in x.xpath("td"):
print stringify(y)
Output:
New row
test
New row
test2
The following code will, however, get the list you ask for:
print map(stringify, tree.xpath("//table/tr/td"))
Output:
['test', 'test2']
This will find all text elements which are at all descended from a td which is a direct descendant of a tr which is in turn a direct descendant of a table.
(Simply asking for all text() elements will create some funny bugs when run on HTML which contains "<td>Foo <b>bar</b></td>" or similar.)

Related

Convert HTML table to CSV via PHP

I am trying to pull each td element from the html table below and import each element into its own cell in a CSV file.
Here are the two html tables:
<table width="100%" border="0" cellspacing="1" cellpadding="0" bgcolor="#006699">
<tr align="center" class="tableRow1Font">
<td width="7%">WAITLIST</td>
<td width="5%">91630</td>
<td width="11%">
ACCY 2001
</td>
<td width="5%">10</td>
<td width="16%">Intro Financial Accounting</td>
<td width="6%">3.00</td>
<td width="8%"> Zou, Y</td>
<td width="8%"><A HREF="http://www.gwu.edu/~map/building.cfm?BLDG=DUQUES" target="_blank"
>DUQUES</a> 251</td>
<td width="13%">TR<br>09:35AM - 10:50AM</td>
<td width="14%">
01/13/14 - 04/28/14
</td>
<td width="7%">
</td>
</tr>
</table>
<table width="100%" border="0" cellspacing="1" cellpadding="0" bgcolor="#006699">
<tr align="center" class="tableRow2Font">
<td width="7%">WAITLIST</td>
<td width="5%">90003</td>
<td width="11%">
ACCY 2001
</td>
<td width="5%">11</td>
<td width="16%">Intro Financial Accounting</td>
<td width="6%">3.00</td>
<td width="8%"> Zou, Y</td>
<td width="8%"><A HREF="http://www.gwu.edu/~map/building.cfm?BLDG=DUQUES" target="_blank"
>DUQUES</a> 254</td>
<td width="13%">TR<br>11:10AM - 12:25PM</td>
<td width="14%">
1/13/14 - 04/28/14
</td>
<td width="7%">
</td>
</tr>
</table>
I have written code that goes through the tables and pulls the td elements:
foreach($html->find('tr[align=center] td') as $e)
$str .= strip_tags($e->innertext) . ', ';
echo $str;
So how can I extract these elements into a CSV file? In Excel I want it to look like this with each td element in its own cell, starting a new row for each html table:
WAITLIST 91630 ACCY 2001 10 Intro Financial Accounting 3.00 Zou, Y DUQUES 251 TR
WAITLIST 90003 ACCY 2001 11 Intro Financial Accounting 3.00 Zou, Y DUQUES 251 TR
There is a library exist for this. Goto http://phpexcel.codeplex.com/. Download the zip file and in example you would find 17html.php try this code. I hope this will help.
CSV means Comma Separated Values. Thus, as you echo out the data (after running it through your function to strip the <td> tags), put commas in between each piece of data (cell), and a new line where you want the next line to start.
So to use your example above, it should look like this:
WAITLIST,91630,ACCY,2001,10,Intro Financial Accounting,3.00,Zou,Y,DUQUES,251,TR
WAITLIST,90003,ACCY,2001,11,Intro Financial Accounting,3.00,Zou,Y,DUQUES,2,
Keep in mind that when you echo this, you shouldn't have any other html tags or anything.

How to extract data from a table With no Style/Class/ID information

i want to parse specific table for scrapping. the code of the table is given below..
<table class="NormalText" cellspacing="1" cellpadding="2" width="100%" border="0"
bgcolor="#eeeeee">
<tr>
<td width="108" align="center">
Stock No.
</td>
<td width="108" align="center">
<span id="invModule_grid_row18_lblMileage">Mileage</span>
</td>
<td width="108" align="center">
Color
</td>
<td width="76" align="center">
Interior
</td>
<td width="104" align="center">
Transmission
</td>
<td width="110" align="center">
Engine
</td>
</tr>
<tr>
<td width="108" align="center">
1204
</td>
<td width="108" align="center">
161,328
</td>
<td width="108" align="center">
Tan
</td>
<td width="76" align="center">
Leather
</td>
<td width="104" align="center">
Automatic
</td>
<td width="110" align="center">
3.5L V6 DOHC 16V
</td>
</tr>
<tr>
<td colspan="7" height="7">
</td>
</tr>
</table>
and the output i want is
1194 56,200 Blue Vinyl 5 Speed 6.8L V10 SOHC 30V
Questions
Which parsing Technique /Parser is best for this? PHPQuery, simplehtmlparse or xpath?
I am more familiar with domDocument, xpath and php, can it be done using xPath?
if yes, what will be xPath? (I am confused as my required data is in td and td tag has no id or class information attached. Also, on the uper row, which is basically a heading row, td are ther too)
Please guide me
XPath
The following example selects the text from all the td nodes in a table row in a table:
//table/tr[position()>1]/td/text()
You will have to know one of two things if there are other tables on the page:
Gets the last table:
//table[last()]/tr[position()>1]/td/text()
Gets the third table:
//table[2]/tr[position()>1]/td/text()
Gets a table based on an attribute, in this case, when class="NormalText":
//table[#class='NormalText']/tr[position()>1]/td/text()

PHP MySQL populating values from database

lets say i retrieve all of the values where their position belongs to top8.I populate them out in a table and instead of displaying different kinds of values , it displays 3 tables with 3 different values, how is this so? any help so that different values belonging to certain values will all be displayed out? i only need one table with 3 different values.
<?
$facebookID = "top8";
mysql_connect("localhost","root","password") or die(mysql_error());
mysql_select_db("schoutweet") or ie(mysql_error());
$data= mysql_query("SELECT schInitial FROM matchTable WHERE position='".$facebookID."'")
or die(mysql_error());
while($row = mysql_fetch_array($data))
{
?>
<center>
<table border="0" cellspacing="0" cellpadding="0" class="tbl_bracket">
<tr>
<td class="brack_under cell_1"><a href="www.facebook.com"/>team 1.1><?= $row['schInitial']?><a/></td>
<td class="cell_2"> </td>
<td class="cell_3"> </td>
<td class="cell_4"> </td>
<td class="cell_5"> </td>
<td class="cell_6"> </td>
</tr>
<tr>
<td class="brack_under_right_up">team 1.2><?= $row['schInitial']?></</td>
<td class="brack_right"><!--1.2.1--></td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
<tr>
<td> </td>
<td class="brack_right"><!--2.1--></td>
<td class="brack_under"><!--3.1--></td>
<td><!--here?--></td>
<td><!--there?--></td>
<td><!--everywhere?--></td>
</tr>
</table>
</center>
<?
}
?>
</body>
That's because your <table> tag is within the loop! Place the <table> tag outside the while loop.
place your table tags outside the while loop
Because your writing the table tag inside the while loop. Everything inside the loop is done each loop cycle. If you only want to have one table in the output, you'll have to open and close the table outside of the loop, like this:
$data= mysql_query("SELECT schInitial FROM matchTable WHERE position='".$facebookID."'")
or die(mysql_error());
?>
<center>
<table border="0" cellspacing="0" cellpadding="0" class="tbl_bracket">
<?
while($row = mysql_fetch_array($data))
{
?>
<tr>
<td class="brack_under cell_1"><a href="www.facebook.com"/>team 1.1><?= $row['schInitial']?><a/></td>
<td class="cell_2"> </td>
<td class="cell_3"> </td>
<td class="cell_4"> </td>
<td class="cell_5"> </td>
<td class="cell_6"> </td>
</tr>
<tr>
<td class="brack_under_right_up">team 1.2><?= $row['schInitial']?></</td>
<td class="brack_right"><!--1.2.1--></td>
<td> </td>
<td> </td>
<td> </td>
<td> </td>
</tr>
<tr>
<td> </td>
<td class="brack_right"><!--2.1--></td>
<td class="brack_under"><!--3.1--></td>
<td><!--here?--></td>
<td><!--there?--></td>
<td><!--everywhere?--></td>
</tr>
<?
}
?>
</table>
</center>
That will, however, print three rows per loop and therefore per record (but you have references to the table contents in two of them, so I suppose that's what you want?).
Also take care about some not well-formed HTML you have there (e.g. the > character in the expression team 1.1> / team 1.2>. If you want to print the > character to the browser, encode it as HTML entity (> for this case). You also have a probably superfluous </ in the first column of the second row (</</td>).
you need to echo the HTML part as well in the while loop like
echo '<table>';

php replace images with divs

below is the markup im pulling from my database table. basically i want to replace the image
<img src="http://newvision.co.ug/IM/logo_white_big.gif" width="80" style="background-color:white;padding:1px">
to
<div style='background:url(http://newvision.co.ug/IM/logo_white_big.gif) center center no-repeat;width:40px;height:40px'></div>
I dnt wanna use regular expressions just an htmlparser that ships with php
<table>
<tbody>
<tr>
<td valign="top"><a href="http://newvision.co.ug/PA/8/13/748484" target=
"_blank"><img src="http://newvision.co.ug/IM/logo_white_big.gif" width="80"
style="background-color:white;padding:1px" /></a></td>
<td valign="top">
<table>
<tbody>
<tr>
<td></td>
</tr>
<tr>
<td valign="top"><b><a target="_blank" href=
"http://newvision.co.ug/PA/8/13/748484" style="font-size:9pt">The New
Vision Online : Holland withholds sh10b over CHOGM</a></b></td>
</tr>
<tr>
<td valign="top"><a href="http://newvision.co.ug/PA/8/13/748484" style=
"font-size:8pt;color<img src="smilies/worry.gif" alt="worry" />ilver"
target="_blank">http://newvision.co.ug/PA/8/13/748484</a></td>
</tr>
<tr>
<td valign="top" style="font-size:8pt;font-weight:normal">The New Vision
is Uganda's leading daily newspaper.</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
There is no parser that ships with PHP, so use PHPQuery, a way of manipulating the DOM in a JQuery like manner instead. This will allow you to use selectors to easily swap out chunks of HTML.

PHP XPATH within changing XML structure

I've an XML file like this:
<tr class="station">
<td class="realtime">
<span>
15:11
</span>
</td>
</tr>
<tr class="station">
<td class="clock">
15:20
</td>
</tr>
<tr class="station">
<td class="clock">
15:30
</td>
</tr>
<tr class="station">
<td class="realtime">
<span>
15:41
</span>
</td>
</tr>
and I wanna parse it with xpath in php. The xml is been updated and parsed quite often.
I always want to get the first time (in this case 15:11)
The problem is that its not sure whether the surrounding tag is a td by class "clock" or "realtime".
If there is a so surrounding realtime, then there is a span tag within. Otherwise not.
In fact, its always the first "station"-class tag in which the information is, that matters.
So is it possible to tell xpath to just evaluate within this tag?
Is there a good method for doing this in xpath?
(sry for my bad english)
In fact, its always the first
"station"-class tag in which the
information is, that matters. So is it
possible to tell xpath to just
evaluate within this tag?
With this wellformed input source:
<table>
<tr class="station">
<td class="realtime">
<span>
15:41
</span>
</td>
</tr>
<tr class="station">
<td class="clock">
15:20
</td>
</tr>
<tr class="station">
<td class="clock">
15:30
</td>
</tr>
<tr class="station">
<td class="realtime">
<span>
15:41
</span>
</td>
</tr>
</table>
This XPath expression:
/table/tr[#class='station'][1]/td
Note: Just select the element you want and use the proper DOM API method to get the string value. It doesn't matter whether there is a span element or not.
If you want to...
/table/tr[#class='station'][1]/td//text()

Categories