graded = 9/9
These problem sets focus on using the Beautiful Soup library to scrape web pages.
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. In the cell below, I've written some code so that you end up with a variable called html_str
that contains the HTML source code of the page, and a variable document
that stores a Beautiful Soup object.
In [1]:
from bs4 import BeautifulSoup
from urllib.request import urlopen
html_str = urlopen("http://static.decontextualize.com/widgets2016.html").read()
document = BeautifulSoup(html_str, "html.parser")
Now, in the cell below, use Beautiful Soup to write an expression that evaluates to the number of <h3>
tags contained in widgets2016.html
.
In [2]:
h3_tags = document.find_all('h3')
h3_tags_count = 0
for tag in h3_tags:
h3_tags_count = h3_tags_count + 1
print(h3_tags_count)
Now, in the cell below, write an expression or series of statements that displays the telephone number beneath the "Widget Catalog" header.
In [3]:
#inspecting webpace with help of developer tools -- shows infomation is stored in an a tag that has the class 'tel'
a_tags = document.find_all('a', {'class':'tel'})
for tag in a_tags:
print(tag.string)
#Does not return the same: [tag.string for tag in a_tags]
In the cell below, use Beautiful Soup to write some code that prints the names of all the widgets on the page. After your code has executed, widget_names
should evaluate to a list that looks like this (though not necessarily in this order):
Skinner Widget
Widget For Furtiveness
Widget For Strawman
Jittery Widget
Silver Widget
Divided Widget
Manicurist Widget
Infinite Widget
Yellow-Tipped Widget
Unshakable Widget
Self-Knowledge Widget
Widget For Cinema
In [4]:
search_table = document.find_all('table',{'class': 'widgetlist'})
#print(search_table)
tables_content = [table('td', {'class':'wname'}) for table in search_table]
#print(tables_content)
for table in tables_content:
for single_table in table:
print(single_table.string)
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
, wname
, 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': 'C1-9476',
'price': '$2.70',
'quantity': u'512',
'wname': 'Skinner Widget'},
{'partno': 'JDJ-32/V',
'price': '$9.36',
'quantity': '967',
'wname': u'Widget For Furtiveness'},
...several items omitted...
{'partno': '5B-941/F',
'price': '$13.26',
'quantity': '919',
'wname': 'Widget For Cinema'}]
And this expression:
widgets[5]['partno']
... should evaluate to:
LH-74/O
In [85]:
widgets = []
#STEP 1: Find all tr tags, because that's what tds are grouped by
for tr_tags in document.find_all('tr', {'class': 'winfo'}):
#STEP 2: For each tr_tag in tr_tags, make a dict of its td
tr_dict ={}
for td_tags in tr_tags.find_all('td'):
td_tags_class = td_tags['class']
for tag in td_tags_class:
tr_dict[tag] = td_tags.string
#STEP3: add dicts to list
widgets.append(tr_dict)
widgets
#widgets[5]['partno']
Out[85]:
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 [100]:
#had to rename variables as it kept printing the ones from the cell above...
widgetsN = []
for trN_tags in document.find_all('tr', {'class': 'winfo'}):
trN_dict ={}
for tdN_tags in trN_tags.find_all('td'):
tdN_tags_class = tdN_tags['class']
for tagN in tdN_tags_class:
if tagN == 'price':
sliced_tag_string = tdN_tags.string[1:]
trN_dict[tagN] = float(sliced_tag_string)
elif tagN == 'quantity':
trN_dict[tagN] = int(tdN_tags.string)
else:
trN_dict[tagN] = tdN_tags.string
widgetsN.append(trN_dict)
widgetsN
Out[100]:
Great! I hope you're having fun. In the cell below, write an expression or series of statements that uses the widgets
list created in the cell above to calculate the total number of widgets that the factory has in its warehouse.
Expected output: 7928
In [112]:
widget_quantity_list = [element['quantity'] for element in widgetsN]
sum(widget_quantity_list)
Out[112]:
In the cell below, write some Python code that prints the names of widgets whose price is above $9.30.
Expected output:
Widget For Furtiveness
Jittery Widget
Silver Widget
Infinite Widget
Widget For Cinema
In [113]:
for widget in widgetsN:
if widget['price'] > 9.30:
print(widget['wname'])
In the following problem set, you will yet again be working with the data in widgets2016.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 [114]:
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 [115]:
example_doc = BeautifulSoup(example_html, "html.parser")
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[115]:
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 print the part numbers of the widgets that are in the table just beneath the header "Hallowed Widgets."
Expected output:
MZ-556/B
QV-730
T1-9731
5B-941/F
In [116]:
for h3_tags in document.find_all('h3'):
if h3_tags.string == 'Hallowed widgets':
hallowed_table = h3_tags.find_next_sibling('table')
for element in hallowed_table.find_all('td', {'class':'partno'}):
print(element.string)
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:
{'Forensic Widgets': 3,
'Hallowed widgets': 4,
'Mood widgets': 2,
'Wondrous widgets': 3}
In [117]:
category_counts = {}
for x_tags in document.find_all('h3'):
x_table = x_tags.find_next_sibling('table')
tr_info_tags = x_table.find_all('tr', {'class':'winfo'})
category_counts[x_tags.string] = len(tr_info_tags)
category_counts
Out[117]:
Congratulations! You're done.