# Homework assignment #5¶

These problem sets focus on using the Beautiful Soup library to scrape web pages.

## Problem Set #1: Basic scraping¶

I've made a web page for you to scrape. It's available here. The page concerns the catalog of a famous widget company. You'll be answering several questions about this web page. First off, in the cell below, write some code so that you end up with a variable called html_str that contains the HTML source code of the page. I've pre-filled the cell with some code; your job is to write the missing line. When you run the cell, it should print out 2801 (the number of characters in the HTML source code for widgets.html).

In [4]:
import urllib

print len(html_str)

2801


Excellent. Now, in the cell below, use Beautiful Soup to write an expression that evaluates to the number of <h3> tags contained in widgets.html. I've added the Beautiful Soup import statement for you.

In [5]:
from bs4 import BeautifulSoup

document = BeautifulSoup(html_str)
len(document.find_all('h3'))

Out[5]:
3

In the cell below, use Beautiful Soup to write some code that creates a list of the names of all the widgets on the page. I've created a variable with an empty list, widget_names, for you. After your code has executed, widget_names should evaluate to a list that looks like this (though not necessarily in this order):

[u'Skinner Widget',
u'Widget For Furtiveness',
u'Widget For Strawman',
u'Manicurist Widget',
u'Infinite Widget',
u'Yellow-Tipped Widget',
u'Unshakable Widget',
u'Self-Knowledge Widget',
u'Widget For Cinema']
In [6]:
widget_names = []

widget_names = [tag.string for tag in document.find_all('td', attrs={"class": "widgetname"})]

widget_names

Out[6]:
[u'Skinner Widget',
u'Widget For Furtiveness',
u'Widget For Strawman',
u'Manicurist Widget',
u'Infinite Widget',
u'Yellow-Tipped Widget',
u'Unshakable Widget',
u'Self-Knowledge Widget',
u'Widget For Cinema']

## Problem set #2: Of Widgets and Pandas¶

For this problem set, we'll continue to use the HTML page from the previous problem set. In the cell below, I've made an empty list and assigned it to a variable called widgets. Write code that populates this list with dictionaries, one dictionary per widget in the source file. The keys of each dictionary should be partno, widgetname, price, and quantity, and the value for each of the keys should be the value for the corresponding column for each row. After executing the cell, your list should look something like this:

[{'partno': u'C1-9476',
'price': u'$2.70', 'quantity': u'512', 'widgetname': u'Skinner Widget'}, {'partno': u'JDJ-32/V', 'price': u'$9.36',
'quantity': u'967',
'widgetname': u'Widget For Furtiveness'},
...several items omitted...
{'partno': u'5B-941/F',
'price': u'$13.26', 'quantity': u'919', 'widgetname': u'Widget For Cinema'}]  And this expression: widgets[5]['partno']  ... should evaluate to: u'MZ-556/B' In [9]: widgets = [] html_str = urllib.urlopen("http://static.decontextualize.com/widgets.html").read() # your code here document = BeautifulSoup(html_str) tr_tags = document.find_all("tr", attrs={'class': 'widgetinfo'}) for tr_tag in tr_tags: widget_dict = {} for class_ in ("partno", "widgetname", "price", "quantity"): tag = tr_tag.find("td", attrs={'class': class_}) widget_dict[class_] = tag.string widgets.append(widget_dict) # end your code widgets  Out[9]: [{'partno': u'C1-9476', 'price': u'$2.70',
'quantity': u'512',
'widgetname': u'Skinner Widget'},
{'partno': u'JDJ-32/V',
'price': u'$9.36', 'quantity': u'967', 'widgetname': u'Widget For Furtiveness'}, {'partno': u'YP4-325/J', 'price': u'$5.17',
'quantity': u'787',
'widgetname': u'Widget For Strawman'},
{'partno': u'VK-486',
'price': u'$8.97', 'quantity': u'441', 'widgetname': u'Manicurist Widget'}, {'partno': u'R4K-990', 'price': u'$11.73',
'quantity': u'320',
'widgetname': u'Infinite Widget'},
{'partno': u'MZ-556/B',
'price': u'$2.35', 'quantity': u'948', 'widgetname': u'Yellow-Tipped Widget'}, {'partno': u'QV-730', 'price': u'$3.76',
'quantity': u'59',
'widgetname': u'Unshakable Widget'},
{'partno': u'T1-9731',
'price': u'$7.11', 'quantity': u'790', 'widgetname': u'Self-Knowledge Widget'}, {'partno': u'5B-941/F', 'price': u'$13.26',
'quantity': u'919',
'widgetname': u'Widget For Cinema'}]

In the cell below, duplicate your code from the previous question. Modify the code to ensure that the values for price and quantity in each dictionary are floating-point numbers and integers, respectively. I.e., after executing the cell, your code should display something like this:

[{'partno': 'C1-9476',
'price': 2.7,
'quantity': 512,
'widgetname': 'Skinner Widget'},
{'partno': 'JDJ-32/V',
'price': 9.36,
'quantity': 967,
'widgetname': 'Widget For Furtiveness'},
... some items omitted ...
{'partno': '5B-941/F',
'price': 13.26,
'quantity': 919,
'widgetname': 'Widget For Cinema'}]



(Hint: Use the float() and int() functions. You may need to use string slices to convert the price field to a floating-point number.)

In [10]:
widgets = []

document = BeautifulSoup(html_str)
tr_tags = document.find_all("tr", attrs={'class': 'widgetinfo'})
for tr_tag in tr_tags:
widget_dict = {}
for class_ops in (("partno", str), ("widgetname", str), ("price", lambda x: float(x[1:])), ("quantity", int)):
tag = tr_tag.find("td", attrs={'class': class_ops[0]})
widget_dict[class_ops[0]] = class_ops[1](tag.string)
widgets.append(widget_dict)

widgets

Out[10]:
[{'partno': 'C1-9476',
'price': 2.7,
'quantity': 512,
'widgetname': 'Skinner Widget'},
{'partno': 'JDJ-32/V',
'price': 9.36,
'quantity': 967,
'widgetname': 'Widget For Furtiveness'},
{'partno': 'YP4-325/J',
'price': 5.17,
'quantity': 787,
'widgetname': 'Widget For Strawman'},
{'partno': 'VK-486',
'price': 8.97,
'quantity': 441,
'widgetname': 'Manicurist Widget'},
{'partno': 'R4K-990',
'price': 11.73,
'quantity': 320,
'widgetname': 'Infinite Widget'},
{'partno': 'MZ-556/B',
'price': 2.35,
'quantity': 948,
'widgetname': 'Yellow-Tipped Widget'},
{'partno': 'QV-730',
'price': 3.76,
'quantity': 59,
'widgetname': 'Unshakable Widget'},
{'partno': 'T1-9731',
'price': 7.11,
'quantity': 790,
'widgetname': 'Self-Knowledge Widget'},
{'partno': '5B-941/F',
'price': 13.26,
'quantity': 919,
'widgetname': 'Widget For Cinema'}]

Great! I hope you're having fun. For the next problem, you'll be converting the list of dictionaries that you created above into a Pandas data frame. I'll give you the code for doing so for free in the cell below:

In [11]:
import pandas as pd
widgets_df = pd.DataFrame(widgets)
widgets_df

Out[11]:
partno price quantity widgetname
0 C1-9476 2.70 512 Skinner Widget
1 JDJ-32/V 9.36 967 Widget For Furtiveness
2 YP4-325/J 5.17 787 Widget For Strawman
3 VK-486 8.97 441 Manicurist Widget
4 R4K-990 11.73 320 Infinite Widget
5 MZ-556/B 2.35 948 Yellow-Tipped Widget
6 QV-730 3.76 59 Unshakable Widget
7 T1-9731 7.11 790 Self-Knowledge Widget
8 5B-941/F 13.26 919 Widget For Cinema

Now that we've got a data frame, let's have some fun with it. In the cell below, write an expression that uses the widgets_df data frame object to calculate the total number of widgets that the factory has in its warehouse.

In [12]:
widgets_df['quantity'].sum()

Out[12]:
5743

In the cell below, write an expression (or series of expressions) that evaluates to a data frame containing only those widgets whose price is above the mean price for all widgets.

In [13]:
widgets_df[widgets_df['price'] > widgets_df['price'].mean()]

Out[13]:
partno price quantity widgetname
1 JDJ-32/V 9.36 967 Widget For Furtiveness
3 VK-486 8.97 441 Manicurist Widget
4 R4K-990 11.73 320 Infinite Widget
8 5B-941/F 13.26 919 Widget For Cinema

## Problem set #3: Sibling rivalries¶

In the following problem set, you will yet again be working with the data in widgets.html. In order to accomplish the tasks in this problem set, you'll need to learn about Beautiful Soup's .find_next_sibling() method. Here's some information about that method, cribbed from the notes:

Often, the tags we're looking for don't have a distinguishing characteristic, like a class attribute, that allows us to find them using .find() and .find_all(), and the tags also aren't in a parent-child relationship. This can be tricky! For example, take the following HTML snippet, (which I've assigned to a string called example_html):

In [14]:
example_html = """
<h2>Camembert</h2>
<p>A soft cheese made in the Camembert region of France.</p>

<h2>Cheddar</h2>
<p>A yellow cheese made in the Cheddar region of... France, probably, idk whatevs.</p>
"""


If our task was to create a dictionary that maps the name of the cheese to the description that follows in the <p> tag directly afterward, we'd be out of luck. Fortunately, Beautiful Soup has a .find_next_sibling() method, which allows us to search for the next tag that is a sibling of the tag you're calling it on (i.e., the two tags share a parent), that also matches particular criteria. So, for example, to accomplish the task outlined above:

In [15]:
example_doc = BeautifulSoup(example_html)
cheese_dict = {}
for h2_tag in example_doc.find_all('h2'):
cheese_name = h2_tag.string
cheese_desc_tag = h2_tag.find_next_sibling('p')
cheese_dict[cheese_name] = cheese_desc_tag.string

cheese_dict

Out[15]:
{u'Camembert': u'A soft cheese made in the Camembert region of France.',
u'Cheddar': u'A yellow cheese made in the Cheddar region of... France, probably, idk whatevs.'}

With that knowledge in mind, let's go back to our widgets. In the cell below, write code that uses Beautiful Soup, and in particular the .find_next_sibling() method, to find out how many widgets are in the table just beneath the header "Hallowed Widgets." (You can tell by looking at the page that there are four such widgets. But this is a programming class, so we have to write a program to do it.)

In [23]:
hallowed_h3_tag = document.find_all('h3')[2]
table_tag = hallowed_h3_tag.find_next_sibling('table')
len(table_tag.find_all('tr'))

Out[23]:
4

Okay, now, the final task. If you can accomplish this, you are truly an expert web scraper. I'll have little web scraper certificates made up and I'll give you one, if you manage to do this thing. And I know you can do it!

In the cell below, I've created a variable category_counts and assigned to it an empty dictionary. Write code to populate this dictionary so that its keys are "categories" of widgets (e.g., the contents of the <h3> tags on the page: "Forensic Widgets", "Mood widgets", "Hallowed Widgets") and the value for each key is the number of widgets that occur in that category. I.e., after your code has been executed, the dictionary category_counts should look like this:

{u'Forensic Widgets': 3, u'Hallowed widgets': 4, u'Mood widgets': 2}
In [24]:
category_counts = {}

{u'Forensic Widgets': 3, u'Hallowed widgets': 4, u'Mood widgets': 2}