Beautiful Soup Documentation ¶
Beautiful Soup is a Python library for pulling data out of HTML and XML files. It works with your favorite parser to provide idiomatic ways of navigating, searching, and modifying the parse tree. It commonly saves programmers hours or days of work.
These instructions illustrate all major features of Beautiful Soup 4, with examples. I show you what the library is good for, how it works, how to use it, how to make it do what you want, and what to do when it violates your expectations.
The examples in this documentation should work the same way in Python 2.7 and Python 3.2.
You might be looking for the documentation for Beautiful Soup 3 . If so, you should know that Beautiful Soup 3 is no longer being developed, and that Beautiful Soup 4 is recommended for all new projects. If you want to learn about the differences between Beautiful Soup 3 and Beautiful Soup 4, see Porting code to BS4 .
This documentation has been translated into other languages by Beautiful Soup users:
- 这篇文档当然还有中文版.
- このページは日本語で利用できます( 外部リンク )
- 이 문서는 한국어 번역도 가능합니다. ( 외부 링크 )
Getting help ¶
If you have questions about Beautiful Soup, or run into problems, send mail to the discussion group . If your problem involves parsing an HTML document, be sure to mention what the diagnose() function says about that document.
Quick Start ¶
Here’s an HTML document I’ll be using as an example throughout this document. It’s part of a story from Alice in Wonderland :
html_doc = """
<title>The Dormouse's story</title>

The Dormouse's story

Once upon a time there were three little sisters; and their names were
Elsie,
Lacie and
Tillie;
and they lived at the bottom of a well.

...

"""
Running the “three sisters” document through Beautiful Soup gives us a
BeautifulSoup
object, which represents the document as a nested
data structure:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'html.parser')
print(soup.prettify())
#
#
# <title>
# The Dormouse's story
# </title>
#
#
#

#
# The Dormouse's story
#
#

#

# Once upon a time there were three little sisters; and their names were
#
# Elsie
#
# ,
#
# Lacie
#
# and
#
# Tillie
#
# ; and they lived at the bottom of a well.
#

#

# ...
#

#
#
Here are some simple ways to navigate that data structure:
soup.title
# <title>The Dormouse's story</title>
soup.title.name
# u'title'
soup.title.string
# u'The Dormouse's story'
soup.title.parent.name
# u'head'
soup.p
#
The Dormouse's story

soup.p['class']
# u'title'
soup.a
# Elsie
soup.find_all('a')
# [Elsie,
# Lacie,
# Tillie]
soup.find(id="link3")
# Tillie
One common task is extracting all the URLs found within a page’s  tags:
for link in soup.find_all('a'):
print(link.get('href'))
# http://example.com/elsie
# http://example.com/lacie
# http://example.com/tillie
Another common task is extracting all the text from a page:
print(soup.get_text())
# The Dormouse's story
#
# The Dormouse's story
#
# Once upon a time there were three little sisters; and their names were
# Elsie,
# Lacie and
# Tillie;
# and they lived at the bottom of a well.
#
# ...
Does this look like what you need? If so, read on.
Installing Beautiful Soup ¶
If you’re using a recent version of Debian or Ubuntu Linux, you can install Beautiful Soup with the system package manager:
$
apt-get
install
python-bs4
(for Python 2)
$
apt-get
install
python3-bs4
(for Python 3)
Beautiful Soup 4 is published through PyPi, so if you can’t install it
with the system packager, you can install it with
easy_install
or
pip
. The package name is
beautifulsoup4
, and the same package
works on Python 2 and Python 3. Make sure you use the right version of
pip
or
easy_install
for your Python version (these may be named
pip3
and
easy_install3
respectively if you’re using Python 3).
$
easy_install
beautifulsoup4
$
pip
install
beautifulsoup4
(The
BeautifulSoup
package is probably
not
what you want. That’s
the previous major release,
Beautiful Soup 3
. Lots of software uses
BS3, so it’s still available, but if you’re writing new code you
should install
beautifulsoup4
.)
If you don’t have
easy_install
or
pip
installed, you can
download the Beautiful Soup 4 source tarball
and
install it with
setup.py
.
$
python
setup.py
install
If all else fails, the license for Beautiful Soup allows you to
package the entire library with your application. You can download the
tarball, copy its
bs4
directory into your application’s codebase,
and use Beautiful Soup without installing it at all.
I use Python 2.7 and Python 3.2 to develop Beautiful Soup, but it should work with other recent versions.
Problems after installation ¶
Beautiful Soup is packaged as Python 2 code. When you install it for use with Python 3, it’s automatically converted to Python 3 code. If you don’t install the package, the code won’t be converted. There have also been reports on Windows machines of the wrong version being installed.
If you get the
ImportError
“No module named HTMLParser”, your
problem is that you’re running the Python 2 version of the code under
Python 3.
If you get the
ImportError
“No module named html.parser”, your
problem is that you’re running the Python 3 version of the code under
Python 2.
In both cases, your best bet is to completely remove the Beautiful Soup installation from your system (including any directory created when you unzipped the tarball) and try the installation again.
If you get the
SyntaxError
“Invalid syntax” on the line
ROOT_TAG_NAME
=
u'[document]'
, you need to convert the Python 2
code to Python 3. You can do this either by installing the package:
$
python3
setup.py
install
or by manually running Python’s
2to3
conversion script on the
bs4
directory:
$
2to3-3.2
-w
bs4
Installing a parser ¶
Beautiful Soup supports the HTML parser included in Python’s standard library, but it also supports a number of third-party Python parsers. One is the lxml parser . Depending on your setup, you might install lxml with one of these commands:
$
apt-get
install
python-lxml
$
easy_install
lxml
$
pip
install
lxml
Another alternative is the pure-Python html5lib parser , which parses HTML the way a web browser does. Depending on your setup, you might install html5lib with one of these commands:
$
apt-get
install
python-html5lib
$
easy_install
html5lib
$
pip
install
html5lib
This table summarizes the advantages and disadvantages of each parser library:
| Parser | Typical usage | Advantages | Disadvantages |
| Python’s html.parser |
BeautifulSoup(markup,
"html.parser")
|
|
|
| lxml’s HTML parser |
BeautifulSoup(markup,
"lxml")
|
|
|
| lxml’s XML parser |
BeautifulSoup(markup,
"lxml-xml")
BeautifulSoup(markup,
"xml")
|
|
|
| html5lib |
BeautifulSoup(markup,
"html5lib")
|
|
|
If you can, I recommend you install and use lxml for speed. If you’re using a version of Python 2 earlier than 2.7.3, or a version of Python 3 earlier than 3.2.2, it’s essential that you install lxml or html5lib–Python’s built-in HTML parser is just not very good in older versions.
Note that if a document is invalid, different parsers will generate different Beautiful Soup trees for it. See Differences between parsers for details.
Making the soup ¶
To parse a document, pass it into the
BeautifulSoup
constructor. You can pass in a string or an open filehandle:
from bs4 import BeautifulSoup
with open("index.html") as fp:
soup = BeautifulSoup(fp)
soup = BeautifulSoup("data")
First, the document is converted to Unicode, and HTML entities are converted to Unicode characters:
BeautifulSoup("Sacré bleu!")
Sacré bleu!
Beautiful Soup then parses the document using the best available parser. It will use an HTML parser unless you specifically tell it to use an XML parser. (See Parsing XML .)
Kinds of objects ¶
Beautiful Soup transforms a complex HTML document into a complex tree
of Python objects. But you’ll only ever have to deal with about four
kinds
of objects:
Tag
,
NavigableString
,
BeautifulSoup
,
and
Comment
.
Tag
¶
A
Tag
object corresponds to an XML or HTML tag in the original document:
soup = BeautifulSoup('Extremely bold')
tag = soup.b
type(tag)
# <class 'bs4.element.Tag'>
Tags have a lot of attributes and methods, and I’ll cover most of them in Navigating the tree and Searching the tree . For now, the most important features of a tag are its name and attributes.
Name ¶
Every tag has a name, accessible as
.name
:
tag.name
# u'b'
If you change a tag’s name, the change will be reflected in any HTML markup generated by Beautiful Soup:
tag.name = "blockquote"
tag
#
Extremely bold

Attributes ¶
A tag may have any number of attributes. The tag

has an attribute “id” whose value is
“boldest”. You can access a tag’s attributes by treating the tag like
a dictionary:
tag['id']
# u'boldest'
You can access that dictionary directly as
.attrs
:
tag.attrs
# {u'id': 'boldest'}
You can add, remove, and modify a tag’s attributes. Again, this is done by treating the tag as a dictionary:
tag['id'] = 'verybold'
tag['another-attribute'] = 1
tag
#
del tag['id']
del tag['another-attribute']
tag
#
tag['id']
# KeyError: 'id'
print(tag.get('id'))
# None
Multi-valued attributes ¶
HTML 4 defines a few attributes that can have multiple values. HTML 5
removes a couple of them, but defines a few more. The most common
multi-valued attribute is
class
(that is, a tag can have more than
one CSS class). Others include
rel
,
rev
,
accept-charset
,
headers
, and
accesskey
. Beautiful Soup presents the value(s)
of a multi-valued attribute as a list:
css_soup = BeautifulSoup('

')
css_soup.p['class']
# ["body"]
css_soup = BeautifulSoup('

')
css_soup.p['class']
# ["body", "strikeout"]
If an attribute looks like it has more than one value, but it’s not a multi-valued attribute as defined by any version of the HTML standard, Beautiful Soup will leave the attribute alone:
id_soup = BeautifulSoup('

')
id_soup.p['id']
# 'my id'
When you turn a tag back into a string, multiple attribute values are consolidated:
rel_soup = BeautifulSoup('
Back to the homepage
')
rel_soup.a['rel']
# ['index']
rel_soup.a['rel'] = ['index', 'contents']
print(rel_soup.p)
#
Back to the homepage

You can use
`get_attribute_list
to get a value that’s always a list,
string, whether or not it’s a multi-valued atribute
id_soup.p.get_attribute_list(‘id’) # [“my id”]
If you parse a document as XML, there are no multi-valued attributes:
xml_soup = BeautifulSoup('

', 'xml')
xml_soup.p['class']
# u'body strikeout'
BeautifulSoup
¶
The
BeautifulSoup
object itself represents the document as a
whole. For most purposes, you can treat it as a
Tag
object. This means it supports most of the methods described in
Navigating the tree
and
Searching the tree
.
Since the
BeautifulSoup
object doesn’t correspond to an actual
HTML or XML tag, it has no name and no attributes. But sometimes it’s
useful to look at its
.name
, so it’s been given the special
.name
“[document]”:
soup.name
# u'[document]'
Comments and other special strings ¶
Tag
,
NavigableString
, and
BeautifulSoup
cover almost
everything you’ll see in an HTML or XML file, but there are a few
leftover bits. The only one you’ll probably ever need to worry about
is the comment:
markup = ""
soup = BeautifulSoup(markup)
comment = soup.b.string
type(comment)
# <class 'bs4.element.Comment'>
The
Comment
object is just a special type of
NavigableString
:
comment
# u'Hey, buddy. Want to buy a used parser'
But when it appears as part of an HTML document, a
Comment
is
displayed with special formatting:
print(soup.b.prettify())
#
#
#
Beautiful Soup defines classes for anything else that might show up in
an XML document:
CData
,
ProcessingInstruction
,
Declaration
, and
Doctype
. Just like
Comment
, these classes
are subclasses of
NavigableString
that add something extra to the
string. Here’s an example that replaces the comment with a CDATA
block:
from bs4 import CData
cdata = CData("A CDATA block")
comment.replace_with(cdata)
print(soup.b.prettify())
#
# <![CDATA[A CDATA block]]>
#
Searching the tree ¶
Beautiful Soup defines a lot of methods for searching the parse tree,
but they’re all very similar. I’m going to spend a lot of time explaining
the two most popular methods:
find()
and
find_all()
. The other
methods take almost exactly the same arguments, so I’ll just cover
them briefly.
Once again, I’ll be using the “three sisters” document as an example:
html_doc = """
<title>The Dormouse's story</title>

The Dormouse's story

Once upon a time there were three little sisters; and their names were
Elsie,
Lacie and
Tillie;
and they lived at the bottom of a well.

...

"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'html.parser')
By passing in a filter to an argument like
find_all()
, you can
zoom in on the parts of the document you’re interested in.
Kinds of filters ¶
Before talking in detail about
find_all()
and similar methods, I
want to show examples of different filters you can pass into these
methods. These filters show up again and again, throughout the
search API. You can use them to filter based on a tag’s name,
on its attributes, on the text of a string, or on some combination of
these.
A string ¶
The simplest filter is a string. Pass a string to a search method and Beautiful Soup will perform a match against that exact string. This code finds all the  tags in the document:
soup.find_all('b')
# [The Dormouse's story]
If you pass in a byte string, Beautiful Soup will assume the string is encoded as UTF-8. You can avoid this by passing in a Unicode string instead.
 This code
finds all the tags whose names start with the letter “b”; in this
case, the  tag and the  tag:
import re
for tag in soup.find_all(re.compile("^b")):
print(tag.name)
# body
# b
This code finds all the tags whose names contain the letter ‘t’:
for tag in soup.find_all(re.compile("t")):
print(tag.name)
# html
# title
A list ¶
If you pass in a list, Beautiful Soup will allow a string match against any item in that list. This code finds all the  tags and all the  tags:
soup.find_all(["a", "b"])
# [The Dormouse's story,
# Elsie,
# Lacie,
# Tillie]
True
¶
The value
True
matches everything it can. This code finds
all
the tags in the document, but none of the text strings:
for tag in soup.find_all(True):
print(tag.name)
# html
# head
# title
# body
# p
# b
# p
# a
# a
# a
# p
A function ¶
If none of the other matches work for you, define a function that
takes an element as its only argument. The function should return
True
if the argument matches, and
False
otherwise.
Here’s a function that returns
True
if a tag defines the “class”
attribute but doesn’t define the “id” attribute:
def has_class_but_no_id(tag):
return tag.has_attr('class') and not tag.has_attr('id')
Pass this function into
find_all()
and you’ll pick up all the

tags:
soup.find_all(has_class_but_no_id)
# [
The Dormouse's story
,
#
Once upon a time there were...
,
#
...
]
This function only picks up the
 tags. It doesn’t pick up the  tags, because those tags define both “class” and “id”. It doesn’t pick up tags like  and <title>, because those tags don’t define “class”.
If you pass in a function to filter on a specific attribute like
href
, the argument passed into the function will be the attribute
value, not the whole tag. Here’s a function that finds all
a
tags
whose
href
attribute
does not
match a regular expression:
def not_lacie(href):
return href and not re.compile("lacie").search(href)
soup.find_all(href=not_lacie)
# [Elsie,
# Tillie]
The function can be as complicated as you need it to be. Here’s a
function that returns
True
if a tag is surrounded by string
objects:
from bs4 import NavigableString
def surrounded_by_strings(tag):
return (isinstance(tag.next_element, NavigableString)
and isinstance(tag.previous_element, NavigableString))
for tag in soup.find_all(surrounded_by_strings):
print tag.name
# p
# a
# a
# a
# p
Now we’re ready to look at the search methods in detail.
find_all()
¶
Signature: find_all( name , attrs , recursive , string , limit , **kwargs )
The
find_all()
method looks through a tag’s descendants and
retrieves
all
descendants that match your filters. I gave several
examples in
Kinds of filters
, but here are a few more:
soup.find_all("title")
# [<title>The Dormouse's story</title>]
soup.find_all("p", "title")
# [
The Dormouse's story
]
soup.find_all("a")
# [Elsie,
# Lacie,
# Tillie]
soup.find_all(id="link2")
# [Lacie]
import re
soup.find(string=re.compile("sisters"))
# u'Once upon a time there were three little sisters; and their names were\n'
Some of these should look familiar, but others are new. What does it
mean to pass in a value for
string
, or
id
? Why does
find_all("p",
"title")
find a
 tag with the CSS class “title”?
Let’s look at the arguments to
find_all()
.
The
name
argument
¶
Pass in a value for
name
and you’ll tell Beautiful Soup to only
consider tags with certain names. Text strings will be ignored, as
will tags whose names that don’t match.
This is the simplest usage:
soup.find_all("title")
# [<title>The Dormouse's story</title>]
Recall from
Kinds of filters
that the value to
name
can be
a
string
,
a regular expression
,
a list
,
a function
, or
the value
True
.
The keyword arguments ¶
Any argument that’s not recognized will be turned into a filter on one
of a tag’s attributes. If you pass in a value for an argument called
id
,
Beautiful Soup will filter against each tag’s ‘id’ attribute:
soup.find_all(id='link2')
# [Lacie]
If you pass in a value for
href
, Beautiful Soup will filter
against each tag’s ‘href’ attribute:
soup.find_all(href=re.compile("elsie"))
# [Elsie]
You can filter an attribute based on a string , a regular expression , a list , a function , or the value True .
This code finds all tags whose
id
attribute has a value,
regardless of what the value is:
soup.find_all(id=True)
# [Elsie,
# Lacie,
# Tillie]
You can filter multiple attributes at once by passing in more than one keyword argument:
soup.find_all(href=re.compile("elsie"), id='link1')
# [three]
Some attributes, like the data-* attributes in HTML 5, have names that can’t be used as the names of keyword arguments:
data_soup = BeautifulSoup('
foo!
')
data_soup.find_all(data-foo="value")
# SyntaxError: keyword can't be an expression
You can use these attributes in searches by putting them into a
dictionary and passing the dictionary into
find_all()
as the
attrs
argument:
data_soup.find_all(attrs={"data-foo": "value"})
# [
foo!
]
You can’t use a keyword argument to search for HTML’s ‘name’ element,
because Beautiful Soup uses the
name
argument to contain the name
of the tag itself. Instead, you can give a value to ‘name’ in the
attrs
argument:
name_soup = BeautifulSoup('<input name="email"/>')
name_soup.find_all(name="email")
# []
name_soup.find_all(attrs={"name": "email"})
# [<input name="email"/>]
Searching by CSS class ¶
It’s very useful to search for a tag that has a certain CSS class, but
the name of the CSS attribute, “class”, is a reserved word in
Python. Using
class
as a keyword argument will give you a syntax
error. As of Beautiful Soup 4.1.2, you can search by CSS class using
the keyword argument
class_
:
soup.find_all("a", class_="sister")
# [Elsie,
# Lacie,
# Tillie]
As with any keyword argument, you can pass
class_
a string, a regular
expression, a function, or
True
:
soup.find_all(class_=re.compile("itl"))
# [
The Dormouse's story
]
def has_six_characters(css_class):
return css_class is not None and len(css_class) == 6
soup.find_all(class_=has_six_characters)
# [Elsie,
# Lacie,
# Tillie]
Remember that a single tag can have multiple values for its “class” attribute. When you search for a tag that matches a certain CSS class, you’re matching against any of its CSS classes:
css_soup = BeautifulSoup('

')
css_soup.find_all("p", class_="strikeout")
# [

]
css_soup.find_all("p", class_="body")
# [

]
You can also search for the exact string value of the
class
attribute:
css_soup.find_all("p", class_="body strikeout")
# [

]
But searching for variants of the string value won’t work:
css_soup.find_all("p", class_="strikeout body")
# []
If you want to search for tags that match two or more CSS classes, you should use a CSS selector:
css_soup.select("p.strikeout.body")
# [

]
In older versions of Beautiful Soup, which don’t have the
class_
shortcut, you can use the
attrs
trick mentioned above. Create a
dictionary whose value for “class” is the string (or regular
expression, or whatever) you want to search for:
soup.find_all("a", attrs={"class": "sister"})
# [Elsie,
# Lacie,
# Tillie]
The
string
argument
¶
With
string
you can search for strings instead of tags. As with
name
and the keyword arguments, you can pass in
a string
,
a
regular expression
,
a list
,
a function
, or
the value True
.
Here are some examples:
soup.find_all(string="Elsie")
# [u'Elsie']
soup.find_all(string=["Tillie", "Elsie", "Lacie"])
# [u'Elsie', u'Lacie', u'Tillie']
soup.find_all(string=re.compile("Dormouse"))
[u"The Dormouse's story", u"The Dormouse's story"]
def is_the_only_string_within_a_tag(s):
"""Return True if this string is the only child of its parent tag."""
return (s == s.parent.string)
soup.find_all(string=is_the_only_string_within_a_tag)
# [u"The Dormouse's story", u"The Dormouse's story", u'Elsie', u'Lacie', u'Tillie', u'...']
Although
string
is for finding strings, you can combine it with
arguments that find tags: Beautiful Soup will find all tags whose
.string
matches your value for
string
. This code finds the
tags whose
.string
is “Elsie”:
soup.find_all("a", string="Elsie")
# [Elsie]
The
string
argument is new in Beautiful Soup 4.4.0. In earlier
versions it was called
text
:
soup.find_all("a", text="Elsie")
# [Elsie]
The
limit
argument
¶
find_all()
returns all the tags and strings that match your
filters. This can take a while if the document is large. If you don’t
need
all
the results, you can pass in a number for
limit
. This
works just like the LIMIT keyword in SQL. It tells Beautiful Soup to
stop gathering results after it’s found a certain number.
There are three links in the “three sisters” document, but this code only finds the first two:
soup.find_all("a", limit=2)
# [Elsie,
# Lacie]
The
recursive
argument
¶
If you call
mytag.find_all()
, Beautiful Soup will examine all the
descendants of
mytag
: its children, its children’s children, and
so on. If you only want Beautiful Soup to consider direct children,
you can pass in
recursive=False
. See the difference here:
soup.html.find_all("title")
# [<title>The Dormouse's story</title>]
soup.html.find_all("title", recursive=False)
# []
Here’s that part of the document:

<title>
The Dormouse's story
</title>

...
The <title> tag is beneath the  tag, but it’s not
directly
beneath the  tag: the  tag is in the way. Beautiful Soup
finds the <title> tag when it’s allowed to look at all descendants of
the  tag, but when
recursive=False
restricts it to the
 tag’s immediate children, it finds nothing.
Beautiful Soup offers a lot of tree-searching methods (covered below),
and they mostly take the same arguments as
find_all()
:
name
,
attrs
,
string
,
limit
, and the keyword arguments. But the
recursive
argument is different:
find_all()
and
find()
are
the only methods that support it. Passing
recursive=False
into a
method like
find_parents()
wouldn’t be very useful.
Calling a tag is like calling
find_all()
¶
Because
find_all()
is the most popular method in the Beautiful
Soup search API, you can use a shortcut for it. If you treat the
BeautifulSoup
object or a
Tag
object as though it were a
function, then it’s the same as calling
find_all()
on that
object. These two lines of code are equivalent:
soup.find_all("a")
soup("a")
These two lines are also equivalent:
soup.title.find_all(string=True)
soup.title(string=True)
find()
¶
Signature: find( name , attrs , recursive , string , **kwargs )
The
find_all()
method scans the entire document looking for
results, but sometimes you only want to find one result. If you know a
document only has one  tag, it’s a waste of time to scan the
entire document looking for more. Rather than passing in
limit=1
every time you call
find_all
, you can use the
find()
method. These two lines of code are
nearly
equivalent:
soup.find_all('title', limit=1)
# [<title>The Dormouse's story</title>]
soup.find('title')
# <title>The Dormouse's story</title>
The only difference is that
find_all()
returns a list containing
the single result, and
find()
just returns the result.
If
find_all()
can’t find anything, it returns an empty list. If
find()
can’t find anything, it returns
None
:
print(soup.find("nosuchtag"))
# None
Remember the
soup.head.title
trick from
Navigating using tag
names
? That trick works by repeatedly calling
find()
:
soup.head.title
# <title>The Dormouse's story</title>
soup.find("head").find("title")
# <title>The Dormouse's story</title>
find_parents()
and
find_parent()
¶
Signature: find_parents( name , attrs , string , limit , **kwargs )
Signature: find_parent( name , attrs , string , **kwargs )
I spent a lot of time above covering
find_all()
and
find()
. The Beautiful Soup API defines ten other methods for
searching the tree, but don’t be afraid. Five of these methods are
basically the same as
find_all()
, and the other five are basically
the same as
find()
. The only differences are in what parts of the
tree they search.
First let’s consider
find_parents()
and
find_parent()
. Remember that
find_all()
and
find()
work
their way down the tree, looking at tag’s descendants. These methods
do the opposite: they work their way
up
the tree, looking at a tag’s
(or a string’s) parents. Let’s try them out, starting from a string
buried deep in the “three daughters” document:
a_string = soup.find(string="Lacie")
a_string
# u'Lacie'
a_string.find_parents("a")
# [Lacie]
a_string.find_parent("p")
#
Once upon a time there were three little sisters; and their names were
# Elsie,
# Lacie and
# Tillie;
# and they lived at the bottom of a well.

a_string.find_parents("p", class="title")
# []
One of the three  tags is the direct parent of the string in
question, so our search finds it. One of the three
 tags is an
indirect parent of the string, and our search finds that as
well. There’s a
 tag with the CSS class “title”
somewhere
in the
document, but it’s not one of this string’s parents, so we can’t find
it with
find_parents()
.
You may have made the connection between
find_parent()
and
find_parents()
, and the
.parent
and
.parents
attributes
mentioned earlier. The connection is very strong. These search methods
actually use
.parents
to iterate over all the parents, and check
each one against the provided filter to see if it matches.
find_next_siblings()
and
find_next_sibling()
¶
Signature: find_next_siblings( name , attrs , string , limit , **kwargs )
Signature: find_next_sibling( name , attrs , string , **kwargs )
These methods use
.next_siblings
to
iterate over the rest of an element’s siblings in the tree. The
find_next_siblings()
method returns all the siblings that match,
and
find_next_sibling()
only returns the first one:
first_link = soup.a
first_link
# Elsie
first_link.find_next_siblings("a")
# [Lacie,
# Tillie]
first_story_paragraph = soup.find("p", "story")
first_story_paragraph.find_next_sibling("p")
#
...

find_previous_siblings()
and
find_previous_sibling()
¶
Signature: find_previous_siblings( name , attrs , string , limit , **kwargs )
Signature: find_previous_sibling( name , attrs , string , **kwargs )
These methods use
.previous_siblings
to iterate over an element’s
siblings that precede it in the tree. The
find_previous_siblings()
method returns all the siblings that match, and
find_previous_sibling()
only returns the first one:
last_link = soup.find("a", id="link3")
last_link
# Tillie
last_link.find_previous_siblings("a")
# [Lacie,
# Elsie]
first_story_paragraph = soup.find("p", "story")
first_story_paragraph.find_previous_sibling("p")
#
The Dormouse's story

find_all_next()
and
find_next()
¶
Signature: find_all_next( name , attrs , string , limit , **kwargs )
Signature: find_next( name , attrs , string , **kwargs )
These methods use
.next_elements
to
iterate over whatever tags and strings that come after it in the
document. The
find_all_next()
method returns all matches, and
find_next()
only returns the first match:
first_link = soup.a
first_link
# Elsie
first_link.find_all_next(string=True)
# [u'Elsie', u',\n', u'Lacie', u' and\n', u'Tillie',
# u';\nand they lived at the bottom of a well.', u'\n\n', u'...', u'\n']
first_link.find_next("p")
#
...

In the first example, the string “Elsie” showed up, even though it was contained within the  tag we started from. In the second example, the last
 tag in the document showed up, even though it’s not in the same part of the tree as the  tag we started from. For these methods, all that matters is that an element match the filter, and show up later in the document than the starting element.
find_all_previous()
and
find_previous()
¶
Signature: find_all_previous( name , attrs , string , limit , **kwargs )
Signature: find_previous( name , attrs , string , **kwargs )
These methods use
.previous_elements
to
iterate over the tags and strings that came before it in the
document. The
find_all_previous()
method returns all matches, and
find_previous()
only returns the first match:
first_link = soup.a
first_link
# Elsie
first_link.find_all_previous("p")
# [
Once upon a time there were three little sisters; ...
,
#
The Dormouse's story
]
first_link.find_previous("title")
# <title>The Dormouse's story</title>
The call to
find_all_previous("p")
found the first paragraph in
the document (the one with class=”title”), but it also finds the
second paragraph, the
 tag that contains the  tag we started
with. This shouldn’t be too surprising: we’re looking at all the tags
that show up earlier in the document than the one we started with. A

 tag that contains an  tag must have shown up before the
tag it contains.
CSS selectors ¶
Beautiful Soup supports the most commonly-used CSS selectors. Just
pass a string into the
.select()
method of a
Tag
object or the
BeautifulSoup
object itself.
You can find tags:
soup.select("title")
# [<title>The Dormouse's story</title>]
soup.select("p:nth-of-type(3)")
# [
...
]
Find tags beneath other tags:
soup.select("body a")
# [Elsie,
# Lacie,
# Tillie]
soup.select("html head title")
# [<title>The Dormouse's story</title>]
Find tags directly beneath other tags:
soup.select("head > title")
# [<title>The Dormouse's story</title>]
soup.select("p > a")
# [Elsie,
# Lacie,
# Tillie]
soup.select("p > a:nth-of-type(2)")
# [Lacie]
soup.select("p > #link1")
# [Elsie]
soup.select("body > a")
# []
Find the siblings of tags:
soup.select("#link1 ~ .sister")
# [Lacie,
# Tillie]
soup.select("#link1 + .sister")
# [Lacie]
Find tags by CSS class:
soup.select(".sister")
# [Elsie,
# Lacie,
# Tillie]
soup.select("[class~=sister]")
# [Elsie,
# Lacie,
# Tillie]
Find tags by ID:
soup.select("#link1")
# [Elsie]
soup.select("a#link2")
# [Lacie]
Find tags that match any selector from a list of selectors:
soup.select("#link1,#link2")
# [Elsie,
# Lacie]
Test for the existence of an attribute:
soup.select('a[href]')
# [Elsie,
# Lacie,
# Tillie]
Find tags by attribute value:
soup.select('a[href="http://example.com/elsie"]')
# [Elsie]
soup.select('a[href^="http://example.com/"]')
# [Elsie,
# Lacie,
# Tillie]
soup.select('a[href$="tillie"]')
# [Tillie]
soup.select('a[href*=".com/el"]')
# [Elsie]
Match language codes:
multilingual_markup = """

Hello

Howdy, y'all

Pip-pip, old fruit

Bonjour mes amis

"""
multilingual_soup = BeautifulSoup(multilingual_markup)
multilingual_soup.select('p[lang|=en]')
# [
Hello
,
#
Howdy, y'all
,
#
Pip-pip, old fruit
]
Find only the first tag that matches a selector:
soup.select_one(".sister")
# Elsie
This is all a convenience for users who know the CSS selector syntax. You can do all this stuff with the Beautiful Soup API. And if CSS selectors are all you need, you might as well use lxml directly: it’s a lot faster, and it supports more CSS selectors. But this lets you combine simple CSS selectors with the Beautiful Soup API.
Modifying the tree ¶
Beautiful Soup’s main strength is in searching the parse tree, but you can also modify the tree and write your changes as a new HTML or XML document.
Changing tag names and attributes ¶
I covered this earlier, in Attributes , but it bears repeating. You can rename a tag, change the values of its attributes, add new attributes, and delete attributes:
soup = BeautifulSoup('Extremely bold')
tag = soup.b
tag.name = "blockquote"
tag['class'] = 'verybold'
tag['id'] = 1
tag
#
Extremely bold

del tag['class']
del tag['id']
tag
#
Extremely bold

Modifying
.string
¶
If you set a tag’s
.string
attribute, the tag’s contents are
replaced with the string you give:
markup = 'I linked to example.com'
soup = BeautifulSoup(markup)
tag = soup.a
tag.string = "New link text."
tag
# New link text.
Be careful: if the tag contained other tags, they and all their contents will be destroyed.
append()
¶
You can add to a tag’s contents with
Tag.append()
. It works just
like calling
.append()
on a Python list:
soup = BeautifulSoup("Foo")
soup.a.append("Bar")
soup
# FooBar
soup.a.contents
# [u'Foo', u'Bar']
insert()
¶
Tag.insert()
is just like
Tag.append()
, except the new element
doesn’t necessarily go at the end of its parent’s
.contents
. It’ll be inserted at whatever numeric position you
say. It works just like
.insert()
on a Python list:
markup = 'I linked to example.com'
soup = BeautifulSoup(markup)
tag = soup.a
tag.insert(1, "but did not endorse ")
tag
# I linked to but did not endorse example.com
tag.contents
# [u'I linked to ', u'but did not endorse', example.com]
insert_before()
and
insert_after()
¶
The
insert_before()
method inserts a tag or string immediately
before something else in the parse tree:
soup = BeautifulSoup("stop")
tag = soup.new_tag("i")
tag.string = "Don't"
soup.b.string.insert_before(tag)
soup.b
# Don'tstop
The
insert_after()
method moves a tag or string so that it
immediately follows something else in the parse tree:
soup.b.i.insert_after(soup.new_string(" ever "))
soup.b
# Don't ever stop
soup.b.contents
# [Don't, u' ever ', u'stop']
clear()
¶
Tag.clear()
removes the contents of a tag:
markup = 'I linked to example.com'
soup = BeautifulSoup(markup)
tag = soup.a
tag.clear()
tag
#
extract()
¶
PageElement.extract()
removes a tag or string from the tree. It
returns the tag or string that was extracted:
markup = 'I linked to example.com'
soup = BeautifulSoup(markup)
a_tag = soup.a
i_tag = soup.i.extract()
a_tag
# I linked to
i_tag
# example.com
print(i_tag.parent)
None
At this point you effectively have two parse trees: one rooted at the
BeautifulSoup
object you used to parse the document, and one rooted
at the tag that was extracted. You can go on to call
extract
on
a child of the element you extracted:
my_string = i_tag.string.extract()
my_string
# u'example.com'
print(my_string.parent)
# None
i_tag
#
decompose()
¶
Tag.decompose()
removes a tag from the tree, then
completely
destroys it and its contents
:
markup = 'I linked to example.com'
soup = BeautifulSoup(markup)
a_tag = soup.a
soup.i.decompose()
a_tag
# I linked to
replace_with()
¶
PageElement.replace_with()
removes a tag or string from the tree,
and replaces it with the tag or string of your choice:
markup = 'I linked to example.com'
soup = BeautifulSoup(markup)
a_tag = soup.a
new_tag = soup.new_tag("b")
new_tag.string = "example.net"
a_tag.i.replace_with(new_tag)
a_tag
# I linked to example.net
replace_with()
returns the tag or string that was replaced, so
that you can examine it or add it back to another part of the tree.
wrap()
¶
PageElement.wrap()
wraps an element in the tag you specify. It
returns the new wrapper:
soup = BeautifulSoup("
I wish I was bold.
")
soup.p.string.wrap(soup.new_tag("b"))
# I wish I was bold.
soup.p.wrap(soup.new_tag("div")
#

I wish I was bold.

This method is new in Beautiful Soup 4.0.5.
unwrap()
¶
Tag.unwrap()
is the opposite of
wrap()
. It replaces a tag with
whatever’s inside that tag. It’s good for stripping out markup:
markup = 'I linked to example.com'
soup = BeautifulSoup(markup)
a_tag = soup.a
a_tag.i.unwrap()
a_tag
# I linked to example.com
Like
replace_with()
,
unwrap()
returns the tag
that was replaced.
Output ¶
Pretty-printing ¶
The
prettify()
method will turn a Beautiful Soup parse tree into a
nicely formatted Unicode string, with each HTML/XML tag on its own line:
markup = 'I linked to example.com'
soup = BeautifulSoup(markup)
soup.prettify()
# '\n \n \n \n \n...'
print(soup.prettify())
#
#
#
#
#
# I linked to
#
# example.com
#
#
#
#
You can call
prettify()
on the top-level
BeautifulSoup
object,
or on any of its
Tag
objects:
print(soup.a.prettify())
#
# I linked to
#
# example.com
#
#
Non-pretty printing ¶
If you just want a string, with no fancy formatting, you can call
unicode()
or
str()
on a
BeautifulSoup
object, or a
Tag
within it:
str(soup)
# 'I linked to example.com'
unicode(soup.a)
# u'I linked to example.com'
The
str()
function returns a string encoded in UTF-8. See
Encodings
for other options.
You can also call
encode()
to get a bytestring, and
decode()
to get Unicode.
Output formatters ¶
If you give Beautiful Soup a document that contains HTML entities like “&lquot;”, they’ll be converted to Unicode characters:
soup = BeautifulSoup("“Dammit!” he said.")
unicode(soup)
# u'\u201cDammit!\u201d he said.'
If you then convert the document to a string, the Unicode characters will be encoded as UTF-8.  These get turned into “&”, “<”, and “>”, so that Beautiful Soup doesn’t inadvertently generate invalid HTML or XML:
soup = BeautifulSoup("
The law firm of Dewey, Cheatem, & Howe
")
soup.p
#
The law firm of Dewey, Cheatem, & Howe

soup = BeautifulSoup('A link')
soup.a
# A link
You can change this behavior by providing a value for the
formatter
argument to
prettify()
,
encode()
, or
decode()
. Beautiful Soup recognizes six possible values for
formatter
.
The default is
formatter="minimal"
. Strings will only be processed
enough to ensure that Beautiful Soup generates valid HTML/XML:
french = "
Il a dit <<Sacré bleu!>>
"
soup = BeautifulSoup(french)
print(soup.prettify(formatter="minimal"))
#
#
#

# Il a dit <<Sacré bleu!>>
#

#
#
If you pass in
formatter="html"
, Beautiful Soup will convert
Unicode characters to HTML entities whenever possible:
print(soup.prettify(formatter="html"))
#
#
#

# Il a dit <<Sacré bleu!>>
#

#
#
If you pass in ``formatter="html5"``, it's the same as
formatter="html5"
, but Beautiful Soup will
omit the closing slash in HTML void tags like “br”:
soup = BeautifulSoup("
")
print(soup.encode(formatter="html"))
#

print(soup.encode(formatter="html5"))
#

If you pass in
formatter=None
, Beautiful Soup will not modify
strings at all on output. This is the fastest option, but it may lead
to Beautiful Soup generating invalid HTML/XML, as in these examples:
print(soup.prettify(formatter=None))
#
#
#

# Il a dit <<Sacré bleu!>>
#

#
#
link_soup = BeautifulSoup('A link')
print(link_soup.a.encode(formatter=None))
# A link
Finally, if you pass in a function for
formatter
, Beautiful Soup
will call that function once for every string and attribute value in
the document. You can do whatever you want in this function. Here’s a
formatter that converts strings to uppercase and does absolutely
nothing else:
def uppercase(str):
return str.upper()
print(soup.prettify(formatter=uppercase))
#
#
#

# IL A DIT <<SACRÉ BLEU!>>
#

#
#
print(link_soup.a.prettify(formatter=uppercase))
#
# A LINK
#
If you’re writing your own function, you should know about the
EntitySubstitution
class in the
bs4.dammit
module. This class
implements Beautiful Soup’s standard formatters as class methods: the
“html” formatter is
EntitySubstitution.substitute_html
, and the
“minimal” formatter is
EntitySubstitution.substitute_xml
. You can
use these functions to simulate
formatter=html
or
formatter==minimal
, but then do something extra.
Here’s an example that replaces Unicode characters with HTML entities whenever possible, but also converts all strings to uppercase:
from bs4.dammit import EntitySubstitution
def uppercase_and_substitute_html_entities(str):
return EntitySubstitution.substitute_html(str.upper())
print(soup.prettify(formatter=uppercase_and_substitute_html_entities))
#
#
#

# IL A DIT <<SACRÉ BLEU!>>
#

#
#
One last caveat: if you create a
CData
object, the text inside
that object is always presented
exactly as it appears, with no
formatting
. Beautiful Soup will call the formatter method, just in
case you’ve written a custom method that counts all the strings in the
document or something, but it will ignore the return value:
from bs4.element import CData
soup = BeautifulSoup("")
soup.a.string = CData("one < three")
print(soup.a.prettify(formatter="xml"))
#
# <![CDATA[one < three]]>
#
get_text()
¶
If you only want the text part of a document or tag, you can use the
get_text()
method. It returns all the text in a document or
beneath a tag, as a single Unicode string:
markup = '\nI linked to example.com\n'
soup = BeautifulSoup(markup)
soup.get_text()
u'\nI linked to example.com\n'
soup.i.get_text()
u'example.com'
You can specify a string to be used to join the bits of text together:
# soup.get_text("|")
u'\nI linked to |example.com|\n'
You can tell Beautiful Soup to strip whitespace from the beginning and end of each bit of text:
# soup.get_text("|", strip=True)
u'I linked to|example.com'
But at that point you might want to use the .stripped_strings generator instead, and process the text yourself:
[text for text in soup.stripped_strings]
# [u'I linked to', u'example.com']
Specifying the parser to use ¶
If you just need to parse some HTML, you can dump the markup into the
BeautifulSoup
constructor, and it’ll probably be fine. Beautiful
Soup will pick a parser for you and parse the data. But there are a
few additional arguments you can pass in to the constructor to change
which parser is used.
The first argument to the
BeautifulSoup
constructor is a string or
an open filehandle–the markup you want parsed. The second argument is
how
you’d like the markup parsed.
If you don’t specify anything, you’ll get the best HTML parser that’s installed. Beautiful Soup ranks lxml’s parser as being the best, then html5lib’s, then Python’s built-in parser. You can override this by specifying one of the following:
- What type of markup you want to parse. Currently supported are “html”, “xml”, and “html5”.
- The name of the parser library you want to use. Currently supported options are “lxml”, “html5lib”, and “html.parser” (Python’s built-in HTML parser).
The section Installing a parser contrasts the supported parsers.
If you don’t have an appropriate parser installed, Beautiful Soup will ignore your request and pick a different parser. Right now, the only supported XML parser is lxml. If you don’t have lxml installed, asking for an XML parser won’t give you one, and asking for “lxml” won’t work either.
Differences between parsers ¶
Beautiful Soup presents the same interface to a number of different parsers, but each parser is different. Different parsers will create different parse trees from the same document. The biggest differences are between the HTML parsers and the XML parsers. Here’s a short document, parsed as HTML:
BeautifulSoup("")
#
Since an empty  tag is not valid HTML, the parser turns it into a  tag pair.
Here’s the same document parsed as XML (running this requires that you have lxml installed). Note that the empty  tag is left alone, and that the document is given an XML declaration instead of being put into an  tag.:
BeautifulSoup("", "xml")
# <?xml version="1.0" encoding="utf-8"?>
#
There are also differences between HTML parsers. If you give Beautiful Soup a perfectly-formed HTML document, these differences won’t matter. One parser will be faster than another, but they’ll all give you a data structure that looks exactly like the original HTML document.
But if the document is not perfectly-formed, different parsers will give different results. Here’s a short, invalid document parsed using lxml’s HTML parser. Note that the dangling
 tag is simply ignored:
BeautifulSoup("
", "lxml")
#
Here’s the same document parsed using html5lib:
BeautifulSoup("
", "html5lib")
#

Instead of ignoring the dangling
 tag, html5lib pairs it with an opening
 tag. This parser also adds an empty  tag to the document.
Here’s the same document parsed with Python’s built-in HTML parser:
BeautifulSoup("
", "html.parser")
#
Like html5lib, this parser ignores the closing
 tag. Unlike html5lib, this parser makes no attempt to create a well-formed HTML document by adding a  tag. Unlike lxml, it doesn’t even bother to add an  tag.
Since the document “
” is invalid, none of these techniques is the “correct” way to handle it. The html5lib parser uses techniques that are part of the HTML5 standard, so it has the best claim on being the “correct” way, but all three techniques are legitimate.
Differences between parsers can affect your script. If you’re planning
on distributing your script to other people, or running it on multiple
machines, you should specify a parser in the
BeautifulSoup
constructor. That will reduce the chances that your users parse a
document differently from the way you parse it.
Encodings ¶
Any HTML or XML document is written in a specific encoding like ASCII or UTF-8. But when you load that document into Beautiful Soup, you’ll discover it’s been converted to Unicode:
markup = "
Sacr\xc3\xa9 bleu!
"
soup = BeautifulSoup(markup)
soup.h1
#
Sacré bleu!

soup.h1.string
# u'Sacr\xe9 bleu!'
It’s not magic. (That sure would be nice.) Beautiful Soup uses a
sub-library called
Unicode, Dammit
to detect a document’s encoding
and convert it to Unicode. The autodetected encoding is available as
the
.original_encoding
attribute of the
BeautifulSoup
object:
soup.original_encoding
'utf-8'
Unicode, Dammit guesses correctly most of the time, but sometimes it
makes mistakes. Sometimes it guesses correctly, but only after a
byte-by-byte search of the document that takes a very long time. If
you happen to know a document’s encoding ahead of time, you can avoid
mistakes and delays by passing it to the
BeautifulSoup
constructor
as
from_encoding
.
Here’s a document written in ISO-8859-8. The document is so short that Unicode, Dammit can’t get a lock on it, and misidentifies it as ISO-8859-7:
markup = b"
\xed\xe5\xec\xf9
"
soup = BeautifulSoup(markup)
soup.h1

νεμω

soup.original_encoding
'ISO-8859-7'
We can fix this by passing in the correct
from_encoding
:
soup = BeautifulSoup(markup, from_encoding="iso-8859-8")
soup.h1

םולש

soup.original_encoding
'iso8859-8'
If you don’t know what the correct encoding is, but you know that
Unicode, Dammit is guessing wrong, you can pass the wrong guesses in
as
exclude_encodings
:
soup = BeautifulSoup(markup, exclude_encodings=["ISO-8859-7"])
soup.h1

םולש

soup.original_encoding
'WINDOWS-1255'
Windows-1255 isn’t 100% correct, but that encoding is a compatible
superset of ISO-8859-8, so it’s close enough. (
exclude_encodings
is a new feature in Beautiful Soup 4.4.0.)
In rare cases (usually when a UTF-8 document contains text written in
a completely different encoding), the only way to get Unicode may be
to replace some characters with the special Unicode character
“REPLACEMENT CHARACTER” (U+FFFD, �). If Unicode, Dammit needs to do
this, it will set the
.contains_replacement_characters
attribute
to
True
on the
UnicodeDammit
or
BeautifulSoup
object. This
lets you know that the Unicode representation is not an exact
representation of the original–some data was lost. If a document
contains �, but
.contains_replacement_characters
is
False
,
you’ll know that the � was there originally (as it is in this
paragraph) and doesn’t stand in for missing data.
Output encoding ¶
When you write out a document from Beautiful Soup, you get a UTF-8 document, even if the document wasn’t in UTF-8 to begin with. Here’s a document written in the Latin-1 encoding:
markup = b'''

<meta content="text/html; charset=ISO-Latin-1" http-equiv="Content-type" />

Sacr\xe9 bleu!

'''
soup = BeautifulSoup(markup)
print(soup.prettify())
#
#
# <meta content="text/html; charset=utf-8" http-equiv="Content-type" />
#
#
#

# Sacré bleu!
#

#
#
Note that the <meta> tag has been rewritten to reflect the fact that the document is now in UTF-8.
If you don’t want UTF-8, you can pass an encoding into
prettify()
:
print(soup.prettify("latin-1"))
#
#
# <meta content="text/html; charset=latin-1" http-equiv="Content-type" />
# ...
You can also call encode() on the
BeautifulSoup
object, or any
element in the soup, just as if it were a Python string:
soup.p.encode("latin-1")
# '
Sacr\xe9 bleu!
'
soup.p.encode("utf-8")
# '
Sacr\xc3\xa9 bleu!
'
Any characters that can’t be represented in your chosen encoding will be converted into numeric XML entity references. Here’s a document that includes the Unicode character SNOWMAN:
markup = u"\N{SNOWMAN}"
snowman_soup = BeautifulSoup(markup)
tag = snowman_soup.b
The SNOWMAN character can be part of a UTF-8 document (it looks like ☃), but there’s no representation for that character in ISO-Latin-1 or ASCII, so it’s converted into “☃” for those encodings:
print(tag.encode("utf-8"))
# ☃
print tag.encode("latin-1")
# ☃
print tag.encode("ascii")
# ☃
Unicode, Dammit ¶
You can use Unicode, Dammit without using Beautiful Soup. It’s useful whenever you have data in an unknown encoding and you just want it to become Unicode:
from bs4 import UnicodeDammit
dammit = UnicodeDammit("Sacr\xc3\xa9 bleu!")
print(dammit.unicode_markup)
# Sacré bleu!
dammit.original_encoding
# 'utf-8'
Unicode, Dammit’s guesses will get a lot more accurate if you install
the
chardet
or
cchardet
Python libraries. The more data you
give Unicode, Dammit, the more accurately it will guess. If you have
your own suspicions as to what the encoding might be, you can pass
them in as a list:
dammit = UnicodeDammit("Sacr\xe9 bleu!", ["latin-1", "iso-8859-1"])
print(dammit.unicode_markup)
# Sacré bleu!
dammit.original_encoding
# 'latin-1'
Unicode, Dammit has two special features that Beautiful Soup doesn’t use.
Smart quotes ¶
You can use Unicode, Dammit to convert Microsoft smart quotes to HTML or XML entities:
markup = b"
I just \x93love\x94 Microsoft Word\x92s smart quotes
"
UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="html").unicode_markup
# u'
I just “love” Microsoft Word’s smart quotes
'
UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="xml").unicode_markup
# u'
I just “love” Microsoft Word’s smart quotes
'
You can also convert Microsoft smart quotes to ASCII quotes:
UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="ascii").unicode_markup
# u'
I just "love" Microsoft Word\'s smart quotes
'
Hopefully you’ll find this feature useful, but Beautiful Soup doesn’t use it. Beautiful Soup prefers the default behavior, which is to convert Microsoft smart quotes to Unicode characters along with everything else:
UnicodeDammit(markup, ["windows-1252"]).unicode_markup
# u'
I just \u201clove\u201d Microsoft Word\u2019s smart quotes
'
Inconsistent encodings ¶
Sometimes a document is mostly in UTF-8, but contains Windows-1252
characters such as (again) Microsoft smart quotes. This can happen
when a website includes data from multiple sources. You can use
UnicodeDammit.detwingle()
to turn such a document into pure
UTF-8. Here’s a simple example:
snowmen = (u"\N{SNOWMAN}" * 3)
quote = (u"\N{LEFT DOUBLE QUOTATION MARK}I like snowmen!\N{RIGHT DOUBLE QUOTATION MARK}")
doc = snowmen.encode("utf8") + quote.encode("windows_1252")
This document is a mess. The snowmen are in UTF-8 and the quotes are in Windows-1252. You can display the snowmen or the quotes, but not both:
print(doc)
# ☃☃☃�I like snowmen!�
print(doc.decode("windows-1252"))
# â˜ƒâ˜ƒâ˜ƒ“I like snowmen!”
Decoding the document as UTF-8 raises a
UnicodeDecodeError
, and
decoding it as Windows-1252 gives you gibberish. Fortunately,
UnicodeDammit.detwingle()
will convert the string to pure UTF-8,
allowing you to decode it to Unicode and display the snowmen and quote
marks simultaneously:
new_doc = UnicodeDammit.detwingle(doc)
print(new_doc.decode("utf8"))
# ☃☃☃“I like snowmen!”
UnicodeDammit.detwingle()
only knows how to handle Windows-1252
embedded in UTF-8 (or vice versa, I suppose), but this is the most
common case.
Note that you must know to call
UnicodeDammit.detwingle()
on your
data before passing it into
BeautifulSoup
or the
UnicodeDammit
constructor. Beautiful Soup assumes that a document has a single
encoding, whatever it might be. If you pass it a document that
contains both UTF-8 and Windows-1252, it’s likely to think the whole
document is Windows-1252, and the document will come out looking like
â˜ƒâ˜ƒâ˜ƒ“I
like
snowmen!”
.
UnicodeDammit.detwingle()
is new in Beautiful Soup 4.1.0.
Comparing objects for equality ¶
Beautiful Soup says that two
NavigableString
or
Tag
objects
are equal when they represent the same HTML or XML markup. In this
example, the two  tags are treated as equal, even though they live
in different parts of the object tree, because they both look like
“pizza”:
markup = "
I want pizza and more pizza!
"
soup = BeautifulSoup(markup, 'html.parser')
first_b, second_b = soup.find_all('b')
print first_b == second_b
# True
print first_b.previous_element == second_b.previous_element
# False
If you want to see whether two variables refer to exactly the same object, use is :
print first_b is second_b
# False
Copying Beautiful Soup objects ¶
You can use
copy.copy()
to create a copy of any
Tag
or
NavigableString
:
import copy
p_copy = copy.copy(soup.p)
print p_copy
#
I want pizza and more pizza!

The copy is considered equal to the original, since it represents the same markup as the original, but it’s not the same object:
print soup.p == p_copy
# True
print soup.p is p_copy
# False
The only real difference is that the copy is completely detached from
the original Beautiful Soup object tree, just as if
extract()
had
been called on it:
print p_copy.parent
# None
This is because two different
Tag
objects can’t occupy the same
space at the same time.
Parsing only part of a document ¶
Let’s say you want to use Beautiful Soup look at a document’s
tags. It’s a waste of time and memory to parse the entire document and
then go over it again looking for  tags. It would be much faster to
ignore everything that wasn’t an  tag in the first place. The
SoupStrainer
class allows you to choose which parts of an incoming
document are parsed. You just create a
SoupStrainer
and pass it in
to the
BeautifulSoup
constructor as the
parse_only
argument.
(Note that this feature won’t work if you’re using the html5lib parser . If you use html5lib, the whole document will be parsed, no matter what. This is because html5lib constantly rearranges the parse tree as it works, and if some part of the document didn’t actually make it into the parse tree, it’ll crash. To avoid confusion, in the examples below I’ll be forcing Beautiful Soup to use Python’s built-in parser.)
SoupStrainer
¶
The
SoupStrainer
class takes the same arguments as a typical
method from
Searching the tree
:
name
,
attrs
,
string
, and
**kwargs
. Here are
three
SoupStrainer
objects:
from bs4 import SoupStrainer
only_a_tags = SoupStrainer("a")
only_tags_with_id_link2 = SoupStrainer(id="link2")
def is_short_string(string):
return len(string) < 10
only_short_strings = SoupStrainer(string=is_short_string)
I’m going to bring back the “three sisters” document one more time,
and we’ll see what the document looks like when it’s parsed with these
three
SoupStrainer
objects:
html_doc = """
<title>The Dormouse's story</title>

The Dormouse's story

Once upon a time there were three little sisters; and their names were
Elsie,
Lacie and
Tillie;
and they lived at the bottom of a well.

...

"""
print(BeautifulSoup(html_doc, "html.parser", parse_only=only_a_tags).prettify())
#
# Elsie
#
#
# Lacie
#
#
# Tillie
#
print(BeautifulSoup(html_doc, "html.parser", parse_only=only_tags_with_id_link2).prettify())
#
# Lacie
#
print(BeautifulSoup(html_doc, "html.parser", parse_only=only_short_strings).prettify())
# Elsie
# ,
# Lacie
# and
# Tillie
# ...
#
You can also pass a
SoupStrainer
into any of the methods covered
in
Searching the tree
. This probably isn’t terribly useful, but I
thought I’d mention it:
soup = BeautifulSoup(html_doc)
soup.find_all(only_short_strings)
# [u'\n\n', u'\n\n', u'Elsie', u',\n', u'Lacie', u' and\n', u'Tillie',
# u'\n\n', u'...', u'\n']
Troubleshooting ¶
diagnose()
¶
If you’re having trouble understanding what Beautiful Soup does to a
document, pass the document into the
diagnose()
function. (New in
Beautiful Soup 4.2.0.) Beautiful Soup will print out a report showing
you how different parsers handle the document, and tell you if you’re
missing a parser that Beautiful Soup could be using:
from bs4.diagnose import diagnose
with open("bad.html") as fp:
data = fp.read()
diagnose(data)
# Diagnostic running on Beautiful Soup 4.2.0
# Python version 2.7.3 (default, Aug 1 2012, 05:16:07)
# I noticed that html5lib is not installed. Installing it may help.
# Found lxml version <IP_ADDRESS>
#
# Trying to parse your data with html.parser
# Here's what html.parser did with the document:
# ...
Just looking at the output of diagnose() may show you how to solve the
problem. Even if not, you can paste the output of
diagnose()
when
asking for help.
Errors when parsing a document ¶
There are two different kinds of parse errors. There are crashes,
where you feed a document to Beautiful Soup and it raises an
exception, usually an
HTMLParser.HTMLParseError
. And there is
unexpected behavior, where a Beautiful Soup parse tree looks a lot
different than the document used to create it.
Almost none of these problems turn out to be problems with Beautiful Soup. This is not because Beautiful Soup is an amazingly well-written piece of software. It’s because Beautiful Soup doesn’t include any parsing code. Instead, it relies on external parsers. If one parser isn’t working on a certain document, the best solution is to try a different parser. See Installing a parser for details and a parser comparison.
The most common parse errors are
HTMLParser.HTMLParseError:
malformed
start
tag
and
HTMLParser.HTMLParseError:
bad
end
tag
. These are both generated by Python’s built-in HTML parser
library, and the solution is to
install lxml or
html5lib.
The most common type of unexpected behavior is that you can’t find a
tag that you know is in the document. You saw it going in, but
find_all()
returns
[]
or
find()
returns
None
. This is
another common problem with Python’s built-in HTML parser, which
sometimes skips tags it doesn’t understand. Again, the solution is to
install lxml or html5lib.
Version mismatch problems ¶
-
SyntaxError: Invalid syntax
(on the lineROOT_TAG_NAME = u'[document]'
): Caused by running the Python 2 version of Beautiful Soup under Python 3, without converting the code. -
ImportError: No module named HTMLParser
- Caused by running the Python 2 version of Beautiful Soup under Python 3. -
ImportError: No module named html.parser
- Caused by running the Python 3 version of Beautiful Soup under Python 2. -
ImportError: No module named BeautifulSoup
- Caused by running Beautiful Soup 3 code on a system that doesn’t have BS3 installed. Or, by writing Beautiful Soup 4 code without knowing that the package name has changed tobs4
. -
ImportError: No module named bs4
- Caused by running Beautiful Soup 4 code on a system that doesn’t have BS4 installed.
Parsing XML ¶
By default, Beautiful Soup parses documents as HTML. To parse a
document as XML, pass in “xml” as the second argument to the
BeautifulSoup
constructor:
soup = BeautifulSoup(markup, "xml")
You’ll need to have lxml installed .
Other parser problems ¶
-
If your script works on one computer but not another, or in one
virtual environment but not another, or outside the virtual
environment but not inside, it’s probably because the two
environments have different parser libraries available. For example,
you may have developed the script on a computer that has lxml
installed, and then tried to run it on a computer that only has
html5lib installed. See
Differences between parsers
for why this
matters, and fix the problem by mentioning a specific parser library
in the
BeautifulSoup
constructor. - Because HTML tags and attributes are case-insensitive , all three HTML parsers convert tag and attribute names to lowercase. That is, the markup <TAG></TAG> is converted to <tag></tag>. If you want to preserve mixed-case or uppercase tags and attributes, you’ll need to parse the document as XML.
Miscellaneous ¶
-
UnicodeEncodeError: 'charmap' codec can't encode character u'\xfoo' in position bar
(or just about any otherUnicodeEncodeError
) - This is not a problem with Beautiful Soup. This problem shows up in two main situations. First, when you try to print a Unicode character that your console doesn’t know how to display. (See this page on the Python wiki for help.) Second, when you’re writing to a file and you pass in a Unicode character that’s not supported by your default encoding. In this case, the simplest solution is to explicitly encode the Unicode string into UTF-8 withu.encode("utf8")
. -
KeyError: [attr]
- Caused by accessingtag['attr']
when the tag in question doesn’t define theattr
attribute. The most common errors areKeyError: 'href'
andKeyError: 'class'
. Usetag.get('attr')
if you’re not sureattr
is defined, just as you would with a Python dictionary. -
AttributeError: 'ResultSet' object has no attribute 'foo'
- This usually happens because you expectedfind_all()
to return a single tag or string. Butfind_all()
returns a _list_ of tags and strings–aResultSet
object. You need to iterate over the list and look at the.foo
of each one. Or, if you really only want one result, you need to usefind()
instead offind_all()
. -
AttributeError: 'NoneType' object has no attribute 'foo'
- This usually happens because you calledfind()
and then tried to access the .foo` attribute of the result. But in your case,find()
didn’t find anything, so it returnedNone
, instead of returning a tag or a string. You need to figure out why yourfind()
call isn’t returning anything.
Improving Performance ¶
Beautiful Soup will never be as fast as the parsers it sits on top of. If response time is critical, if you’re paying for computer time by the hour, or if there’s any other reason why computer time is more valuable than programmer time, you should forget about Beautiful Soup and work directly atop lxml .
That said, there are things you can do to speed up Beautiful Soup. If you’re not using lxml as the underlying parser, my advice is to start . Beautiful Soup parses documents significantly faster using lxml than using html.parser or html5lib.
You can speed up encoding detection significantly by installing the cchardet library.
Parsing only part of a document won’t save you much time parsing the document, but it can save a lot of memory, and it’ll make searching the document much faster.
Beautiful Soup 3 ¶
Beautiful Soup 3 is the previous release series, and is no longer being actively developed. It’s currently packaged with all major Linux distributions:
$
apt-get
install
python-beautifulsoup
It’s also published through PyPi as
BeautifulSoup
.:
$
easy_install
BeautifulSoup
$
pip
install
BeautifulSoup
You can also download a tarball of Beautiful Soup 3.2.0 .
If you ran
easy_install
beautifulsoup
or
easy_install
BeautifulSoup
, but your code doesn’t work, you installed Beautiful
Soup 3 by mistake. You need to run
easy_install
beautifulsoup4
.
The documentation for Beautiful Soup 3 is archived online .
Porting code to BS4 ¶
Most code written against Beautiful Soup 3 will work against Beautiful
Soup 4 with one simple change. All you should have to do is change the
package name from
BeautifulSoup
to
bs4
. So this:
from BeautifulSoup import BeautifulSoup
becomes this:
from bs4 import BeautifulSoup
-
If you get the
ImportError
“No module named BeautifulSoup”, your problem is that you’re trying to run Beautiful Soup 3 code, but you only have Beautiful Soup 4 installed. -
If you get the
ImportError
“No module named bs4”, your problem is that you’re trying to run Beautiful Soup 4 code, but you only have Beautiful Soup 3 installed.
Although BS4 is mostly backwards-compatible with BS3, most of its methods have been deprecated and given new names for PEP 8 compliance . There are numerous other renames and changes, and a few of them break backwards compatibility.
Here’s what you’ll need to know to convert your BS3 code and habits to BS4:
You need a parser ¶
Beautiful Soup 3 used Python’s
SGMLParser
, a module that was
deprecated and removed in Python 3.0. Beautiful Soup 4 uses
html.parser
by default, but you can plug in lxml or html5lib and
use that instead. See
Installing a parser
for a comparison.
Since
html.parser
is not the same parser as
SGMLParser
, you
may find that Beautiful Soup 4 gives you a different parse tree than
Beautiful Soup 3 for the same markup. If you swap out
html.parser
for lxml or html5lib, you may find that the parse tree changes yet
again. If this happens, you’ll need to update your scraping code to
deal with the new tree.
Method names ¶
-
renderContents
->encode_contents
-
replaceWith
->replace_with
-
replaceWithChildren
->unwrap
-
findAll
->find_all
-
findAllNext
->find_all_next
-
findAllPrevious
->find_all_previous
-
findNext
->find_next
-
findNextSibling
->find_next_sibling
-
findNextSiblings
->find_next_siblings
-
findParent
->find_parent
-
findParents
->find_parents
-
findPrevious
->find_previous
-
findPreviousSibling
->find_previous_sibling
-
findPreviousSiblings
->find_previous_siblings
-
nextSibling
->next_sibling
-
previousSibling
->previous_sibling
Some arguments to the Beautiful Soup constructor were renamed for the same reasons:
-
BeautifulSoup(parseOnlyThese=...)
->BeautifulSoup(parse_only=...)
-
BeautifulSoup(fromEncoding=...)
->BeautifulSoup(from_encoding=...)
I renamed one method for compatibility with Python 3:
-
Tag.has_key()
->Tag.has_attr()
I renamed one attribute to use more accurate terminology:
-
Tag.isSelfClosing
->Tag.is_empty_element
I renamed three attributes to avoid using words that have special meaning to Python. Unlike the others, these changes are not backwards compatible. If you used these attributes in BS3, your code will break on BS4 until you change them.
-
UnicodeDammit.unicode
->UnicodeDammit.unicode_markup
-
Tag.next
->Tag.next_element
-
Tag.previous
->Tag.previous_element
Generators ¶
I gave the generators PEP 8-compliant names, and transformed them into properties:
-
childGenerator()
->children
-
nextGenerator()
->next_elements
-
nextSiblingGenerator()
->next_siblings
-
previousGenerator()
->previous_elements
-
previousSiblingGenerator()
->previous_siblings
-
recursiveChildGenerator()
->descendants
-
parentGenerator()
->parents
So instead of this:
for parent in tag.parentGenerator():
...
You can write this:
for parent in tag.parents:
...
(But the old code will still work.)
Some of the generators used to yield
None
after they were done, and
then stop. That was a bug. Now the generators just stop.
There are two new generators,
.strings and
.stripped_strings
.
.strings
yields
NavigableString objects, and
.stripped_strings
yields Python
strings that have had whitespace stripped.
XML ¶
There is no longer a
BeautifulStoneSoup
class for parsing XML. To
parse XML you pass in “xml” as the second argument to the
BeautifulSoup
constructor. For the same reason, the
BeautifulSoup
constructor no longer recognizes the
isHTML
argument.
Beautiful Soup’s handling of empty-element XML tags has been
improved. Previously when you parsed XML you had to explicitly say
which tags were considered empty-element tags. The
selfClosingTags
argument to the constructor is no longer recognized. Instead,
Beautiful Soup considers any empty tag to be an empty-element tag. If
you add a child to an empty-element tag, it stops being an
empty-element tag.
Entities ¶
An incoming HTML or XML entity is always converted into the
corresponding Unicode character. Beautiful Soup 3 had a number of
overlapping ways of dealing with entities, which have been
removed. The
BeautifulSoup
constructor no longer recognizes the
smartQuotesTo
or
convertEntities
arguments. (
Unicode,
Dammit
still has
smart_quotes_to
, but its default is now to turn
smart quotes into Unicode.) The constants
HTML_ENTITIES
,
XML_ENTITIES
, and
XHTML_ENTITIES
have been removed, since they
configure a feature (transforming some but not all entities into
Unicode characters) that no longer exists.
If you want to turn Unicode characters back into HTML entities on output, rather than turning them into UTF-8 characters, you need to use an output formatter .
Miscellaneous ¶
Tag.string now operates recursively. If tag A contains a single tag B and nothing else, then A.string is the same as B.string. (Previously, it was None.)
Multi-valued attributes
like
class
have lists of strings as
their values, not strings. This may affect the way you search by CSS
class.
If you pass one of the
find*
methods both
string
and
a tag-specific argument like
name
, Beautiful Soup will
search for tags that match your tag-specific criteria and whose
Tag.string
matches your value for
string
. It will
not
find the strings themselves. Previously,
Beautiful Soup ignored the tag-specific arguments and looked for
strings.
The
BeautifulSoup
constructor no longer recognizes the
markupMassage
argument. It’s now the parser’s responsibility to
handle markup correctly.
The rarely-used alternate parser classes like
ICantBelieveItsBeautifulSoup
and
BeautifulSOAP
have been
removed. It’s now the parser’s decision how to handle ambiguous
markup.
The
prettify()
method now returns a Unicode string, not a bytestring.


WATCH: Charlize Theron singing along and fangirling Boyz II Men is the best thing you’ll see today
by Bashiera Parker (@bparks_)Cape Town – Charlize Theron recently shared a video of Boyz II Men performing at a fundraising event and called it the highlight of her life.
The Bombshell star wrote on Instagram, "I’ve gotten to see @boyziimen live twice in one year and honestly can say it’s a highlight of my life.
"SO much fun tonight at our @ctaop fundraising event. Thanks to these incredible dudes for closing out the night in the best way and to everyone who showed up and helped raised funds to protect the youth of South Africa."
In the short video, the Academy Award-winning actress pans the room showing guests clearly enjoying themselves, before the camera switches to her singing along.
In the next clip, she gives her most passionate performance as she sings along to Boyz II Men’s End Of The Road – and that, is the highlight of our lives.
SEE HER PASSIONATE PERFORMANCE HERE:
The fundraising event was for the Charlize Theron Africa Outreach Project (CTAOP), which focuses on investing in youth and their future by fighting HIV in South Africa. The event happened at the Africa Center in New York City and was attended by many celebrity guests, including our very own former Miss Universe, Demi-Leigh Nel-Peters.
Demi shared a video of Charlize talking about the theme of the evening: ubuntu.
"I am because you are. It’s a belief in our universal bond as humans that we’re all interconnected," Charlize explained. "This spirit and the culture of South Africa really shaped who I am today. And I always knew that I wanted to return some of the gift given to me back to the incredible country and people of South Africa."


echo
? Instead of echo $(cmd)
, just use cmd
echo "$(cat 1.txt)"
echo `< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c6`
cat 1.txt # In bash, but faster and still sticks exactly one newline: printf '%s\n' "$(<1.txt)"
# The original `echo` sticks a newline; we want it too.
< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c6; echo
The command substitution $(foo)
yields the result of command foo
with trailing newlines erased, and when it is passed to echo
it generally just gives the same result as foo
.
The command echo "$(false)"
will return true, whereas false
of course returns false – beware of the ignored exit code before blindly altering scripts. If using set -e
, the correct substitution of echo "$(cmd)"
would be cmd || true
.
One may want to use command substitutions plus echo
to make sure there is exactly one trailing newline. The special command substitution $(<file)
in bash
is also un-outline-able.
Anyway, echo is still not that reliable (see [[SC2039#echo-flags]]) and printf
should be used instead.


We're sorry but Tapos na ba ang COVID19-PH doesn't work properly without JavaScript enabled. Please enable it to continue.


shareware
`Prev `__
S
`Next `__
--------------
**shareware**: /sheir´weir/, n.
A kind of `*freeware* <../F/freeware.html>`__ for which the author
requests some payment, usually in the accompanying documentation files
or in an announcement made by the software itself. Such payment may or
may not buy additional support or functionality. See also
`*careware* <../C/careware.html>`__,
`*charityware* <../C/charityware.html>`__,
`*crippleware* <../C/crippleware.html>`__, `*FRS* <../F/FRS.html>`__,
`*guiltware* <../G/guiltware.html>`__,
`*postcardware* <../P/postcardware.html>`__, and
`*-ware* <../W/suffix-ware.html>`__; compare
`*payware* <../P/payware.html>`__.
--------------
+-------------------------------------+----------------------------+--------------------------------------+
| `Prev `__ | `Up <../S.html>`__ | `Next `__ |
+-------------------------------------+----------------------------+--------------------------------------+
| Share and enjoy! | `Home <../index.html>`__ | sharing violation |
+-------------------------------------+----------------------------+--------------------------------------+


Before Change
def test_arma_conv():
in_channels, out_channels = (16, 32)
num_stacks, num_layers = 8, 4
edge_index = torch.tensor([[0, 0, 0, 1, 2, 3], [1, 2, 3, 0, 0, 0]])
num_nodes = edge_index.max().item() + 1
edge_weight = torch.rand(edge_index.size(1))
x = torch.randn((num_nodes, in_channels))
conv = ARMAConv(
in_channels, out_channels, num_stacks, num_layers, dropout=0.25)
After Change
conv = ARMAConv(16, 32, num_stacks=8, num_layers=4)
assert conv.__repr__() == "ARMAConv(16, 32, num_stacks=8, num_layers=4)"
out = conv(x, edge_index)
assert out.size() == (4, 32)
assert conv(x, adj.t()).tolist() == out.tolist()
t = "(Tensor, Tensor, OptTensor) -> Tensor"


Ever wonder what to watch? We've made a great list of films to give you some suggestions and reviews to help you decide!
Deadpool (2016)
With everything from emotional storylines, to action filled storylines, Deadpool has you covered! The story is based upon Marvel Comics’ Wade Wilson and his alter-ego, Deadpool, whilst we follow him on his journey to hunt down the man who ran evil experiments on him and changed his life forever.
As somebody who has not followed the comics before, I found it a little hard to adjust at first and actually understand the storyline and the humour, but after a while, it started to make sense, and it actually felt like we were a part of Wade’s emotions. Director Tim Miller did such a great job getting the audience to be so enthralled into the story. It was really interesting to watch the start of the film and then to be re-joined at the same point later after grasping the story. The only thing that I felt let the film down, was the constant ‘yo-yoing’ of emotion and action. In some ways, this worked, but in others it seemed a little confusing to follow. One minute his tactics caused explosive results and you are enthralled in the action, but then it gets cut to a quiet scene for too long, followed by head action. But the yo-yoing of action and emotion is how Deadpool’s life really is so that could work on two factors.
Overall, I loved the film! I really hope that there is a sequel to it. To say that I have not read the comics before, I did get into the film and enjoy it. So go check it out!
Keith Lemon:The Film (2012)
The Film, is a true Yorkshire gem, and Keith Lemon (Leigh Francis) has done a great job in this film, but the bit that gets to me the most is that the film just proves the ‘North - South Divide, between Yorkshire and London, when Keith visits London trying to plug his invention and it doesn’t particularly work. It really is a silly film but it works, and it works better than some of these serious movies out there.
This film has so many bad reviews out there, but it is definitely in my top five! Perhaps with Leigh Francis being from Yorkshire, it is more Yorkshire kind of humour? Either way, I loved it! Definitely worth a see.
The Inbetweeners (2008-2014)
I have been watching The Inbetweeners lately, the full series and the movies. Series 1 begins with Will, a ‘posh’ boy who’s mum is unable to afford to keep him in private school, and instead, introduces him to public school, and the story is based around him trying to fit in with the other students, but he sticks out like a sore thumb. But the rest of the series’ follows his life with the friends that he does make during their lives in school.
The movies are a little different to the series though. There is more of a story to them. The series is mostly stereotypical into what teenage boys get up to in school and in their free time, but the movies show them in a different environment as they are abroad. Their behaviour on holiday is still stereotypical of them but I just feel like these work better than the series. The second movie is also my favourite; it shows the boys after school and into university as they’ve grown older, which I like because it isn’t cheap trying to keep them in school for a story they try to continue the series. It follows Jay on his gap year in Australia and how ‘the lads’ go and join him, and they get up to lots of mis-adventures. It shows emotion and comedy at the same time. Definitely worth a watch!


Full name:
org.apache.sling:htl-maven-plugin:1.1.8-1.4.0:validate
Description:
Attributes:
| Name | Type | Since | Description |
|---|---|---|---|
| excludes | String[] | 1.0.0 | List of files to exclude. Specified as fileset patterns which are
relative to the input directory whose contents will be scanned (see
the sourceDirectory configuration option). |
| failOnWarnings | boolean | 1.0.0 | If set to "true" it will fail the build on compiler warnings. Default value is: false. User property is: htl.failOnWarnings. |
| generateJavaClasses | boolean | 1.1.0 | If set to "true" it will generate the Java classes resulted from
transpiling the HTL scripts to Java. The generated classes will be
stored in the folder identified by the
generatedJavaClassesDirectory parameter. Default value is: false. User property is: htl.generateJavaClasses. |
| generatedJavaClassesDirectory | File | 1.1.0 | Defines the folder where the generated Java classes resulted from
transpiling the project's HTL scripts will be stored. This folder
will be added to the list of source folders for this project. Default value is: ${project.build.directory}/generated-sources/htl. User property is: htl.generatedJavaClassesDirectory. |
| ignoreImports | Set | 1.1.0 | Defines a list of Java packages that should be ignored when
generating the import statements for the Java classes resulted from
transpiling the project's HTL scripts. Subpackages of these
packages will also be part automatically of the ignore list. User property is: htl.ignoreImports. |
| includes | String[] | 1.0.0 | List of files to include. Specified as fileset patterns which are
relative to the input directory whose contents will be scanned (see
the sourceDirectory configuration option). Default value is: **/*.html. |
| skip | boolean | 1.0.2 | If set to "true" the validation will be skipped. Default value is: false. User property is: htl.skip. |
| sourceDirectory | File | 1.0.0 | Defines the root folder where this Mojo expects to find Sightly
scripts to validate. Default value is: ${project.build.sourceDirectory}. User property is: htl.sourceDirectory. |
generatedJavaClassesDirectory:
skip:


Pakistan to host home tests for first time in decade
MUMBAI: Pakistan will play a test match on home soil for the first time in more than 10 years when Sri Lanka tour the country for a two-match series next month, the country's cricket board (PCB) said on Thursday.
Pakistan has not hosted a test match since a 2009 militant attack on Sri Lanka's team bus in Lahore left six security personnel and two civilians dead and six players injured.
Pakistan did not host any international cricket for six years after the attack, with the team playing their home matches in the United Arab Emirates.
"This is fabulous news for Pakistan and its reputation of being as safe and secure as any other country in the world," said Zakir Khan, the PCB's director of international cricket, in a statement.
Pakistan has hosted a number of limited-overs internationals in recent years, with Sri Lanka playing three one-dayers and three Twenty20 matches there in September and October, though 10 key players opted out of that trip citing security concerns.
A Sri Lanka Cricket (SLC) source told Reuters there was some apprehension about touring Pakistan for the limited-over series but concerns have since eased.
"The board is looking at sending a full-strength team for the test series," the source added.
The first test will be held at Rawalpindi from Dec. 11-15 while Karachi will host the second from Dec. 19-23, the PCB said.
SLC Chief Executive Ashley de Silva said it was time for test cricket to return to Pakistan.
"We are pleased to confirm our return visit to Pakistan as, based on our earlier visit, we are comfortable and convinced conditions are suitable and conducive for test cricket," he said.
"We also believe all cricket playing countries should host international cricket at home and in this relation we are happy to play our part in complete resumption of international cricket in Pakistan ..."
The two boards had agreed to bring forward the limited-overs leg of the split series and pushed back the two tests, which were originally set to take place in October.
(Reporting by Sudipto Ganguly; editing by Peter Rutherford)


Meet-Up Event Planner
Navigate to
Login
Register
Create Event
View Events
Login
Email
Password
Login
New user? Click here to register.


Here you go.
Single tag elements can have a terminating slash.
This HTML is from Benefits of polyglot XHTML" by Jesper Tverskov.


The value of shares in Kylie Cosmetics crashes after Kylie Jenner sold half
Kylie Jenner may have raked in an enormous $600million ($463million) for selling 51% of her Kylie Cosmetics business, but it’s bad news If you own shares in the business as they plummeted following the news.
While beauty giant Coty, who now own the majority of Kylie’s beauty company, did see their stocks increase slightly after the news was announced, they finished the week at the lowest they have been all month.
Following the news, their stock moved up to $12.22 (£9.52), but soon dropped to $11.53 (£8.98), the lowest they’ve been in a month.
TMZ reports that they ended the week down 5.5% on where they were before Kylie cashed out half of her ownership.
Analysts had predicted the value of the shares to surge after Coty snapped Kylie’s best-selling range of products up, so it’ll be a bit of a blow for anyone with serious cash riding on the company.
Advertisement Advertisement
Kim Kardashian recently opened up about how proud she was of Kylie for building the billion-dollar business out of her insecurities about her lips.
She told Entertainment Tonight: ‘I’m so proud of her. To think in her early 20s, she built a business off of something that she first was insecure about and finding confidence and, you know, just tapping into that and figuring out how to make an amazing product and just being true to herself and doing what she loves to do.
‘That’s so inspiring and so amazing and I’m so proud of her. I’ve known that selling her business was always in the works for her for a long time.
‘A lot of companies were bidding and really wanted to buy her brand, and so selling just about half of it was a decision that she made about a year ago and, you know, that she wanted to.’
Got a showbiz story?
If you've got a story, video or pictures get in touch with the Metro.co.uk Entertainment team by emailing us<EMAIL_ADDRESS>calling 020 3615 2145 or by visiting our Submit Stuff page - we'd love to hear from you.


CHOOSE FROM THE OPTIONS BELOW
This game ends after either player reaches 5 points.
ROCK
PAPER
SCISSORS
PLAY


Signing you in...
About
Overlay
Instagram
Contact
etc...
Sign in
with your Google Account
Email or phone
Forgot email?
Not your computer? Use Guest mode to sign in privately.
Create account
Next
Enter your password
Forgot password?
Go back
Sign in


{{movie.name | uppercase}} Year: {{movie.release_year}} Director: {{movie.director}}
Year: {{movie.release_year}}
Director: {{movie.director}}
There aren't actors added to this movie.


Alford leads the way for Alabama A&M over Troy 80-66
HUNSTVILLE, Ala. (AP) - Cameron Alford had 23 points as Alabama A&M; defeated Troy 80-66 on Monday night.
Jalen Johnson had 11 points and five blocks for Alabama A&M; (1-5), which ended its season-opening five-game losing streak. Brandon Miller added 11 points. TJ Parham had 11 points for the hosts.
Zay Williams had 13 points for the Trojans (1-6). Ty Gordon added 10 points. Charles Norman had 10 points and six rebounds.
Darian Adams, the Trojans’ leading scorer entering the contest at 14.0 points per game, shot only 20% in the game (2 of 10).
Alabama A&M; matches up against Jacksonville State at home next Saturday. Troy plays Shorter at home on Saturday.
For more AP college basketball coverage: https://apnews.com/Collegebasketball and http://twitter.com/AP_Top25
This was generated by Automated Insights, http://www.automatedinsights.com/ap, using data from STATS LLC, https://www.stats.com
Sign up for Daily Newsletters
Copyright © 2019 The Washington Times, LLC.
Please read our comment policy before commenting.


s6-portable-utils is a set of tiny general Unix utilities, often performing well-known tasks such as cut and grep, but optimized for simplicity and small size. They were designed for embedded systems and other constrained environments, but they work everywhere.
Other set of small utilities are usually system-specific; for instance, the (otherwise good) BusyBox project only works on Linux.
Some of s6-portable-utils' programs are a conformant implementation of a POSIX utility as determined by the Single Unix Specification, version 4; the relevant documentation pages mentions this. However, none of the s6-portable-utils programs supports internationalization.
It is now an explicit non-goal of s6-portable-utils to duplicate the work of existing projects that aim to provide a lightweight implementation of standard commands. No more rewriting of standard commands will occur in s6-portable-utils; the package is now used to host specific utilities such as s6-uniquename.
Prefixing the name of this package — and the names of the binaries it provides — with s6- was a naming mistake. It happened in the early days of the s6 project, before it grew and became an ecosystem focused on supervision, service management and init; despite sharing the same author and the same philosophy, and being designed to work with it, s6-portable-utils is not, stricto sensu, part of the s6 ecosystem, and if you arrived here while trying to understand what s6 is, you are on the wrong page. I apologize for the confusion.
s6-portable-utils is free software. It is available under the ISC license.
git clone git://git.skarnet.org/s6-portable-utils
All these commands exit 111 if they encounter a temporary error, and 100 if they encounter a permanent error - such as a misuse. They exit 127 if they're trying to execute into a program and cannot find it, and 126 if they fail to execute into a program for another reason.
(Miscellaneous tools)
(Multicall configuration)


Evolution Lawyers is an Auckland-based incorporated law firm specialising in insolvency, commercial, company, and property law. We provide both litigation and transactional services to small-to-medium sized businesses operating in New Zealand.
Continue reading to find out how we’re different. Or email Tamina Cunningham-Adams right now at<EMAIL_ADDRESS>We believe the way lawyers work will change more in the next 50 years than it has in the last 500. With technology advancing faster than ever before, even a profession as old and conservative as law will be forced to adapt. Our philosophy is that embracing this evolution is better than reacting to it. In the end, it’s about ensuring we can always provide the legal services that modern clients require.
Unlike most firms, Evolution Lawyers does not have a central office. Our lawyers work from home offices with remote supervision from Tamina and Thomas, and remote administration and research support. This is not only a massive cost saving for the firm; it means our lawyers can be more efficient, productive, and (gasp) happy. That means better quality services for our clients and much lower staff attrition. All of this is aimed at creating and fostering long-term client relationships.
Having no central premises has absolutely no negative impact on the quality of the legal product. Our lawyers are connected to each other 24/7, are subject to the same rules that would apply in any physical law firm, including best practice rules around management, delegation, and peer-review, and make use of all the benefits of modern technology.
And we mean modern technology. Tamina and Thomas are the founders of CataLex – a company at the cutting edge of legal solutions software. Evolution benefits from its association with CataLex in a number of ways, including by having CataLex develop an online client relationship portal specifically for Evolution. It also means we can ensure our services are truly modern, and of the highest possible standard.
Despite all this talk about technology, we are not a so-called ‘online law firm’. You can actually meet us in person, and we encourage that. We’re happy to come to you, meet at a nice café, or book a meeting or conference room at a convenient location. Whatever works. We just think, for the most part, email, text, phone, and Skype do the trick just fine. They are not only convenient; they save cost and mean we can be more available to our clients.
Evolution is a boutique firm. Our lawyers are true specialists. We provide transactional and litigation services to SMEs in the areas of insolvency, commercial, property, and company law. That’s it. If you need something outside of those areas, we will ensure you have access to our extensive referral network, and can assist with managing your instruction. Simply put, we will never take on an instruction unless we are confident it is within our area of expertise. For us, it is not about collecting all the work we can; it’s about making sure all the work we do is of the highest possible standard.
Let’s talk fees. Most firms charge by the hour – so do we. The difference? We charge different rates according to the service being provided; not the person providing the service. There are three categories, as per the following table:
| Fee category | Rate | Types of work |
|---|---|---|
| A - Core Legal Services | $350 | Original drafting of instruments and claims, writing a legal opinion, providing written legal advice, providing substantive legal advice over the telephone, Skype, or in person, appearing in Court, acting in a negotiation or mediation, and attending a meeting at your request. |
| B - Related Legal Services | $250 | Reviewing documents and evidence, researching the law, considering application of the law, completing a basic precedent document, and correspondence with you, other lawyers, and the Court, including reporting on matter progress, seeking further instructions, and following up deadlines and other known requirements. |
| C - Incidental Services | $150 | Proof-reading, compiling a bundle of documents or authorities, administering a data room, and general matter management. |
Tamina and Thomas initially worked together at a top-tier commercial law firm, before leaving that firm to found CataLex (legal software) in 2014 and Evolution Lawyers (legal services) in 2015. They are long-standing friends and professional colleagues. Tamina, in fact, was moving counsel in Thomas’ admission to the bar. Through Evolution Lawyers, they can finally combine their complimentary skill sets and unique backgrounds to provide a better standard of legal service to the New Zealand business community.
Tamina is the firm’s managing director. She excels at helping clients overcome challenges affecting their business, particularly when it comes to disputes and insolvency matters. She focuses on understanding her clients’ businesses and developing long-term relationships, so as to provide truly valuable legal counsel. At the same time as being director of Evolution and CataLex, Tamina is a practising liquidator of complex corporate assignments and CEO of insolvency firm, Fisher White & Associates. She is also a published author, guest lecturer in commercial law at the University of Auckland, and a qualified teacher.
Thomas is a problem-solver. He enjoys dealing with complex legal matters over a wide range of subject areas - large or small, litigation or transactional. He is also the creative force behind Evolution Lawyers and CataLex, and managing director of the latter. At Evolution, he is in charge of systems administration, quality assurance, process improvement, and operational efficiency. He also develops and maintains the firm’s knowledge assets. Thomas is a published author, with one of his articles being favourably cited in Canadian case law. Before becoming a lawyer, he was an operations’ analyst for a large insurance company and, before that, a musician.
| 0800 ELAWYERS (0800 352 993) | |
|<EMAIL_ADDRESS>| |
| Fax: +64 9 929 3332 | |
| PO Box 40046, Glenfield, Auckland 0747 | |
|
LinkedIn |
|
| facebook.com/evolutionlawyers |


{% extends "layout.html" %} {% block page_title %} GOV.UK prototype kit {% endblock %} {% block content %} Service name Live Dashboard Transactions Settings Service name API keys Email notifications 3D Secure Set up payment page Set up a payment link Reference number Applicant name Email Payment description As it appears on the user facing payment screens Amount owed £ {% endblock %}
Live


Technocruise is the pre-festival Zonal round event of Techkriti which comprises of Preliminary rounds of some major competitions and professional technical workshops to enhance the learning of students and to encourage the zeal of the participants. Technocruise covers all major cities of India and thus spans the entire height and width of the nation. Last year we witnessed a participation of over 8000 in workshops and events from nearly 250 colleges all over India
For further details Mail us at: zonals@techkriti.org


Class Gear.Instance
Inherited Members
System.Object.ToString()
System.Object.Equals(System.Object)
System.Object.Equals(System.Object, System.Object)
System.Object.ReferenceEquals(System.Object, System.Object)
System.Object.GetHashCode()
System.Object.GetType()
System.Object.MemberwiseClone()
Namespace: SpiceSharp.Simulations.IntegrationMethods
Assembly: SpiceSharp.dll
Syntax
protected class Instance : SpiceMethod.SpiceInstance, IBreakpointMethod, IIntegrationMethod, ISimulationState
Constructors
Instance(Gear, IBiasingSimulationState)
Declaration
public Instance(Gear parameters, IBiasingSimulationState state)
Parameters
| Type | Name | Description |
|---|---|---|
| Gear | parameters | |
| IBiasingSimulationState | state |
Fields
Coefficients
Declaration
protected readonly DenseVector<double> Coefficients
Field Value
| Type | Description |
|---|---|
| DenseVector<System.Double> |
PredictionCoefficients
Declaration
protected readonly DenseVector<double> PredictionCoefficients
Field Value
| Type | Description |
|---|---|
| DenseVector<System.Double> |
Solver
Declaration
protected readonly DenseRealSolver Solver
Field Value
| Type | Description |
|---|---|
| DenseRealSolver |
Methods
ComputeCoefficients()
Declaration
protected override void ComputeCoefficients()
Overrides
CreateDerivative(Boolean)
Declaration
public override IDerivative CreateDerivative(bool track)
Parameters
| Type | Name | Description |
|---|---|---|
| System.Boolean | track |
Returns
| Type | Description |
|---|---|
| IDerivative |
Overrides
Initialize()
Declaration
public override void Initialize()
Overrides
Predict()
Declaration
protected override void Predict()