Showing posts with label Content Scraping. Show all posts
Showing posts with label Content Scraping. Show all posts

Friday, 28 February 2014

Fetching a Web page in WEB-HARVEST(PART:2)




<?xml version="1.0" encoding="UTF-8"?>

<config>
    <var-def name="datestring">
          <file action="read" path="date.txt"></file>
    </var-def>
   
    <var-def name="webpage">
         <html-to-xml>
          <http url="http://scores.espn.go.com/ncb/scoreboard?date=20121124"/>
     </html-to-xml>
    </var-def>
    <var-def name="duke">
    <xpath expression="(//div[@class='team visitor'])[1]//a[@title]/text()">
        <var name="webpage"></var>
   </xpath>
   </var-def>
</config>


Today I'll show how to pull information out of the webpage and save it -- specifically, we'll pull the teams and scores out of the page and save them off for later use.

Find the Information

The first step is to figure out where the information we want is in the web page.  This is easier said than done on modern web pages, which tend to be impenetrable morasses of Javascript, HTML and CSS.  One way to get started is to use the "View Source" option on your web browser (or save the web page onto your computer and view it with your favorite text editor) and then search for text you can see from the web page.  For example, if we go the ESPN Scoreboard page for 11/24/2012, we can see that the first listed game is Duke versus Louisville.  If we do "View Source" and search for "Duke", we find this as the first reference:
    <a title="Duke" href="http://espn.go.com/mens-college-basketball/team/_/id/150/duke-blue-devils">Duke</a>
And, this in fact, is the HTML code that creates the "Duke" text in the Duke vs. Louisville scoreboard.  With some more digging, re-formating and so on, we can eventually see that the information about Duke is in an HTML structure that looks like this:
    <div class="team visitor">
      <div class="team-capsule">
          <span id="323290097-aTeamName">
        <a title="Duke">
          Duke</a>
          </span>
      </div>
      <ul id="323290097-aScores" class="score" style="display:block">
        <li class="final" id="323290097-awayHeaderScore">
          76</li>
      </ul>
    </div>
The information about Louisville is in a structure that is identical except that it starts with "team home" instead of "team visitor."

So now that we know where the information is, we need to pluck it out and put it to use.

The Power of XPath

Web-Harvest uses Xpath extensively to dig information out of webpages.  Xpath is a notation for specifying where to find something in an XML file.  It's a "path" from the top-level of the XML down to some particular piece (or pieces) of the XML.  It's very useful and very powerful, but like regular expressions can be confusing and difficult to use.  If you don't know anything about XPath, you might want to go off and read a tutorial about it to familiarize yourself with how it works.  It's also very useful to have an XPath tester for working out the correct paths for the information you're trying to get.

In fact, Web-Harvest itself provides a very handy XPath tester.  To see it's use, run the above script to fetch the ESPN page, and then use the left-hand pane to see the value of the "webpage" variable (also as shown above).  Now click on the magnifier icon to the right of the "[Value]" box and you'll get a pop-up window showing the text of the webpage:


Notice the "View as:" option in the top left of the pop-up.  Click here and select XML.  This will show the webpage in XML format:


This view has a couple of handy features.  First, you can use the "Pretty-Print" button at the top to reorganize and cleanup the XML for easier viewing.  Second, you'llsee a box at the bottom labeled "XPath expression."  If you type an Xpath into this box, Web-Harvest will run that XPath against the displayed XML and show the result.  For example, try typing the Xpath //div[@class="team visitor"] into the box.  This expression finds all div elements in the page that have the class "team visitor":


This matches a total of 15 div elements on this page, the first of which is the Duke entry we found above.

When an Xpath returns a list of items, we can pick items out of the list in various ways, including using an index. To pick out the first element of this list, we use (//div[@class="team visitor"])[1]. That gives us the entire block HTML for Duke that I showed earlier. If you look up there, you'll see the team name is within a <a @title="Duke"> tag. We can pull that out by extending our Xpath to say (//div[@class="team visitor"])[1]//a[@title] which essentially says "Give me all the <a> elements with a title attribute that are within the first div element with a class of team visitor". Try that out:


We've now narrowed the Xpath down to just the <a> element containing the team name. We can extract the actual name by appending the function text()to the end of our Xpath. This function returns whatever text it finds inside the element selected by the Xpath:



Here's how we'd use that same Xpath within Web-Harvest to pull out the name and save it in a variable:



You can experiment with creating the Xpaths to pull out the home team's name and the final scores of the game.

Looping


The Xpath example above works on the first element in the list of visitor team names, but what we really want to do is capture the team names and scores for all the games on the page. To do that, we will loop over each of the game sections in turn. Web-Harvest provides a processor for this called <loop>, which works about as you would imagine. It takes a list of elements and loops over them one at a time, and returns a list of the results. Here's the skeleton for looping over each of the games in turn:



The <loop> processor has two parts. The first part is a <list> of items to loop over. The second party is a <body> that will be executed for each element of the list. Each time the <body> is executed, a variable called currGame (which is specified as "item" in the <loop> tag) will be set to the current element of the list. In this case, each <body> execution just returns the current item, so the result of the loop is just the list.

Notice that the <list> of items is given by the Xpath "(//div[contains(@class,'final-state')])". That Xpath returns a list of div elements. There's one div element for each game on the page, and the div has the team names and scores inside of it. (The visiting team name we pulled out earlier is inside this div.)

So now, each time through the loop we need pull out the team names and scores for currGame. currGame contains a chunk of XML, so we can once again use Xpath to do this. Then we'll store each item in its own variable:



<?xml version="1.0" encoding="UTF-8"?>

<config>
  <var-def name="datestring">
    <file action="read" path="date.txt"></file>
  </var-def>
  <var-def name="webpage">
      <html-to-xml> 
        <http url="http://scores.espn.go.com/ncb/scoreboard?date=20121124"/>
      </html-to-xml>
    </var-def> 
  <loop item="currGame">
      <list>
        <xpath expression="(//div[contains(@class,'final-state')])">
            <var name="webpage"/>
        </xpath>
      </list>
      <body>
          <var-def name="visitor">
              <xpath expression="(//div[@class='team visitor'])[1]//a[@title]/text()">
                  <var name="currGame"/>
              </xpath>
          </var-def>
          <var-def name="visitorScore">
              <xpath expression="(//li[@class='final'])[2]//text()">
                  <var name="currGame"/>
              </xpath>
          </var-def>
          <var-def name="home">
              <xpath expression="(//div[@class='team home'])[1]//a[@title]/text()">
                  <var name="currGame"/>
              </xpath>
          </var-def>
          <var-def name="homeScore">
              <xpath expression="(//li[@class='final'])[3]//text()">
                  <var name="currGame"/>
              </xpath>
          </var-def>
        <file action="append" type="text" path="scores.txt">
           <template>
               ${visitor} ${visitorScore} ${home} ${homeScore} ${sys.cr}${sys.lf}
           </template>
        </file>
      </body>
  </loop>
</config>

Each var-def in the body of the loop uses an Xpath expression to pull out a particular piece of the data. You might want to experiment with the Xpaths to see how each of them finds the right piece of information.

If you run this and look at the value of the loop after it is complete you'll see this:



The value of the loop is a list of all the values of the body as it is executed, and the value of each body is just the list of the values of the processors in the body (four var-def processors in this case). It all gets mashed together and you end up with a long list of team names and scores.

 

Format and Output


To make this more useful, let's clean up the format of the game data and write it out to a file. We can format using the <template> process that we saw last time, and to output we use the same <file> processor we used to read a file. Every time through the loop we'll add a line to the file for the game we just processed:



The ${sys.cr} and ${sys.lf} are Javascript values that put a carriage-return/line-feed at the end of every line. The output file looks like this:




Conclusion


This tutorial should give a general idea of how Web-Harvest works and some of the basic tools it offers for scraping information out of web pages. More help can be found online at the Web-Harvest documentation as well as the Web-Harvest forums.

Here is the completed Web-Harvest script, for cut & paste purposes:

<?xml version="1.0" encoding="UTF-8"?>

<config>
  <var-def name="datestring">
    <file action="read" path="date.txt"></file>
  </var-def>
  <var-def name="webpage">
      <html-to-xml>  
        <http url="http://scores.espn.go.com/ncb/scoreboard?date=20121124"/>
      </html-to-xml>
    </var-def>  
  <loop item="currGame">
      <list>
        <xpath expression="(//div[contains(@class,'final-state')])">
            <var name="webpage"/>
        </xpath>
      </list>
      <body>
          <var-def name="visitor">
              <xpath expression="(//div[@class='team visitor'])[1]//a[@title]/text()">
                  <var name="currGame"/>
              </xpath>
          </var-def>
          <var-def name="visitorScore">
              <xpath expression="(//li[@class='final'])[2]//text()">
                  <var name="currGame"/>
              </xpath>
          </var-def>
          <var-def name="home">
              <xpath expression="(//div[@class='team home'])[1]//a[@title]/text()">
                  <var name="currGame"/>
              </xpath>
          </var-def>
          <var-def name="homeScore">
              <xpath expression="(//li[@class='final'])[3]//text()">
                  <var name="currGame"/>
              </xpath>
          </var-def>
        <file action="append" type="text" path="scores.txt">
           <template>
               ${visitor} ${visitorScore} ${home} ${homeScore} ${sys.cr}${sys.lf}
           </template>
        </file>
      </body>
  </loop>
</config>

Wednesday, 26 February 2014

Fetching a Webpage in WEB-HARVEST(PART:1)

Now we'll be to fetching webpages.  Look at the following script:

<?xml version="1.0" encoding="UTF-8"?>

<config>
    <var-def name="datestring">
          <file action="read" path="date.txt"></file>
    </var-def>
   
    <var-def name="webpage">
         <html-to-xml>
          <http url="http://scores.espn.go.com/ncb/scoreboard?date=$(datestring)"/>
     </html-to-xml>
    </var-def>
   
</config>




As before, we read in the datestring from a file.  And as in Part 1, we use the http processor to fetch a web page.  But notice the url:

url="http://scores.espn.go.com/ncb/scoreboard?date=${datestring}"
The end of the URL is "${datestring}".  In processor attributes, just as in the template processor, anything enclosed in ${ } is evaluated in Javascript.  In this case, "${datestring}" is replaced with the value of the datestring variable -- which is the "20121110" we read from the date.txt file.  So the resulting URL is "http://scores.espn.go.com/ncb/scoreboard?date=20121110".  This leads (as you might have guessed) to the college basketball results from 11/10/2012.

Conclusion

We now have some basic tools for fetching and manipulating data.  Next time we'll get to the real work of pulling information out of a web page.

CONTINUE WITH PART : 2 

Reading a file in Web-Harvest

 Reading a File

To begin with, let's look at how we can read information from a file into Web-Harvest.  In this example, I'm going to assume we have a file in our working directory called "date.txt" and that file contains a single line with a date in the format YYYYMMDD, e.g., 20121110.  Go to your working directory and create that file.  Then open up Web-Harvest, start a new configuration file, and type in this script:
<?xml version="1.0" encoding="UTF-8"?>

<config>
    <var-def name="datestring">
          <file action="read" path="date.txt"></file>
    </var-def>
   
    <var-def name="USdate">
          <regexp>
              <regexp-pattern>
                  ^(/d/d/d/d)(/d/d)(/d/d)
             </regexp-pattern>
             <regexp-source>
                  <var name="datestring"></var>
             </regexp-source>
            
             <regexp-result>
                  <template>${_2}/${_3}/${_1}</template>
             </regexp-result>
           </regexp>
    </var-def>
   
</config>


The file processor reads the contents of the "date.txt" file and provides that as a result to the outer processor.  In this case, that's the var-def processor that is creating the "datestring" variable.  The result is that the datestring variable will be created and its value will be the contents of the date.txt file.  To see this, hit the green "Run" arrow, and then examine the datestring variable:



Regular Expressions

Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems.  (Jamie Zawinski)
Jamie Zawinski's famous and generally sound advice notwithstanding, regular expressions are a significant element in the Web-Harvest toolbox.  This makes sense -- much of what we do in screen scraping is manipulating text, and regular expressions are very good at that task.  It's beyond my interest (and probably, ability) to teach you regular expressions.  You'll have to find other resources for that.  But I recommend using a regular expression tester like this to help you debug your regular expressions.  (Remember that Web-Harvest is implemented in Java, so it uses the Java regular expression syntax.)

For a simple example, we'll use a regular expression to pull the year out of the date we've read from the date.txt file.   The year is the first four digits of the date, and digits in regular expressions are represented as \d.  Here's the script to use a regular expression to pull the year out of the datestring variable and store it in a new variable called year:


This example introduces a couple of new processors.  The first is the regexp processor, which has three parts: the regexp-pattern, the regexp-source, and the regexp-result.  The regexp-pattern portion holds the regular expression we're trying to match.  In this case, it is the expression "^(\d\d\d\d)" which means "a group of four digits at the beginning of a line".  The regexp-source provides the string against which we'll try to match the pattern.  In this case, it is the value of the datestring variable, which is the contents of the date.txt file from the previous step of the configuration file.  Finally, the regexp-result portion determines what the result of the regular expression will be -- that is, what value it will feed back up to the next processor.

As you can see, inside of regexp-result we have another processor -- template.  Template basically returns whatever is inside of it.  So if you wrote <template>Test</template> the result would simply be the string "Test".  However -- and this is the useful part -- anything enclosed inside ${ } will be evaluated in Javascript and the result of the Javascript will be injected into the template.  So if you wrote <template>Today is the ${sys.datetime("dd")}th</template> you'd get back "Today is the 13th" (or whatever the current day is).

Web-Harvest defines a number of useful variables inside Javascript.  One of these is _1, which is the value of the first matched group in a regular expression.  Because the _1 in our template is enclosed in ${ } it is evaluated in Javascript and is replaced with the first matched group in the regular expression.  So in this case, our template returns "2012".

Finally, the regexp processor returns the value of the regexp-result part, and the year variable gets set to "2012".  (As you can see in the above screenshot.)

Here's a slightly more complicated example that uses a regular expression to reformat the date in US format.  See if you can figure it out:

Installing Web-Harvest

Installing Web-Harvest is trivial. Download the latest "Single self-executable JAR file" from the website here .
This contains a single Jar file.  Put that somewhere on your computer and then double-click on the Jar file.  Presuming you have Java correctly installed, after a few moments the Web-Harvest GUI will pop up:



Notice that you can download and open some examples.  Under the Help menu (or with F1) you'll find the Web-Harvest manual.  You can also read this online here.

A Useful Note:  Version 2.0 of Web-Harvest has a memory leak bug.  This can cause the tool to use up all available memory and hang when downloading and processing a large number of web pages.  (Say, a whole season's worth of basketball games :-)  You can somewhat minimize this problem by starting Java with a larger memory allocation, using the "-Xms" and "-Xmx" options.  How to do this will vary slightly depending upon your operating system and whether things are installed.  On my Windows machine, I use a command line that looks something like this:
C:\WINDOWS\system32\javaw.exe -Xms1024m -Xmx1024m -jar "webharvest_all_2.jar"
On Windows you can create a shortcut and set the "Target" to be the proper command line.  However, even with this workaround, Web-Harvest will eventually hang.  The only choice then is to quit and restart.

Initial Set-Up

After you've downloaded and installed Web-Harvest, there are one or two things you should set before continuing.  Open the Web-Harvest GUI as above, and on the Execution menu, select Preferences.  This should open a form like this:


First of all, use this form to set an "Output Path".  This is the folder (directory) where Web-Harvest will look for input files and write output files.  (You can use absolute path names as well, but if you don't, this is where Web-Harvest will try to find things.)  There's no way to change this within your Web-Harvest script, so if you need to change this for different scripts, you'll have to remember to do it here first before running your script.

Second, if you need to use a proxy, this is where you can fill in that information.

Using WEB HARVEST for Content Scraping

"Web scraping" is the process of crawling over a web site, downloading web pages intended for human consumption, extracting information, and saving it in a machine-readable format.  With the advent of the Web 2.0 and services-based architectures, web scraping has largely fallen into disuse, but it is still required/handy in situations such as this.

There are a number of web scraping tools available, with various functionality and state of repair.  Many are frameworks or libraries intended to be embedded in languages like Python.  Others are commercial.  For my purposes, I wanted a stand-alone, open-source tool with fairly powerful features and a GUI interface.  I ended up settling on Web-Harvest. Web-Harvest is written in Java, so it can be run on nearly any platform, and can also be embedded into Java programs.


<?xml version="1.0" encoding="UTF-8"?>

<config>
    <var-def name="google">
     <html-to-xml>
          <http url="http://www.google.com"/>
     </html-to-xml>
    </var-def>
</config>


There are three commands (what Web-Harvest calls "processors") in this configuration file: var-def, html-to-xml, and http.  Reading these from the inside outwards, this is what they do:

  1. The innermost processor, http, fetches the web page given in the url attribute -- in this case, the Google home page.
  2. The next processor, html-to-xml, takes the web page, cleans it up a bit and converts it to XML.
  3. The last processor, var-def, defines a new Web-Harvest variable named google and gives it the value of the XML returned by the html-to-xml processor.
To see this in action, click the green "Run" arrow near the top of the GUI.  Web-Harvest will whir through the script and give you a message that "Configuration 'Config 1' has finished execution."  Click OK

This is just the HTML for the Google home page -- it's what the http processor fetched from the Web.  Try clicking on the "html-to-xml [1]" processor and see how the same web page looks encoded as XML.  (Pretty much the same in this case.)


Conclusion

So far I've shown how to get Web-Harvest installed and to create a simple script to download a web page.  Next time I'll go into some more detail about how to use the various Web-Harvest features to extract and save information from a web page.

Total Pageviews

DjKiRu Initative. Powered by Blogger.