beautifulsoap 中文文档
来源:互联网 发布:炫踪网络 年收入 编辑:程序博客网 时间:2024/06/06 07:48
翻译 by Richie Yan (richieyan@gmail.com)
###如果有些翻译的不准确或者难以理解,直接看例子吧。###
英文原文点这里
Beautiful Soup 是用Python写的一个HTML/XML的解析器,它可以很好的处理不规范标记并生成剖析树(parse tree)。 它提供简单又常用的导航(navigating),搜索以及修改剖析树的操作。它可以大大节省你的编程时间。 对于Ruby,使用Rubyful Soup。
这个文档说明了Beautiful Soup 3.0主要的功能特性,并附有例子。 从中你可以知道这个库有哪些好处,它是怎样工作的, 怎样让它帮做你想做的事以及你该怎样做当它做的和你期待不一样。
目录
- 快速开始
- 剖析文档
- 剖析 HTML
- 剖析 XML
- 如果它不工作
- 使用Unicode的Beautiful Soup, Dammit
- 输出文档
- 剖析树
Tag
s的属性
- Navigating 剖析树
parent
contents
string
nextSibling
andpreviousSibling
next
andprevious
- 遍历
Tag
- 使用标签名作为成员
- Searching 剖析树
- The basic find method:
findAll(name, attrs, recursive, text, limit, **kwargs)
- 使用CSS类查找
- 像
findall
一样调用tag
find(name, attrs, recursive, text, **kwargs)
first
哪里去了?
- The basic find method:
- Searching 剖析树内部
findNextSiblings(name, attrs, text, limit, **kwargs)
andfindNextSibling(name, attrs, text, **kwargs)
findPreviousSiblings(name, attrs, text, limit, **kwargs)
andfindPreviousSibling(name, attrs, text, **kwargs)
findAllNext(name, attrs, text, limit, **kwargs)
andfindNext(name, attrs, text, **kwargs)
findAllPrevious(name, attrs, text, limit, **kwargs)
andfindPrevious(name, attrs, text, **kwargs)
- Modifying 剖析树
- 改变属性值
- 删除元素
- 替换元素
- 添加新元素
- 常见问题(Troubleshooting)
- 为什么Beautiful Soup不能打印我的no-ASCII字符?
- Beautiful Soup 弄丢了我给的数据!为什么?为什么?????
- Beautiful Soup 太慢了!
- 高级主题
- 产生器(Generators)
- 其他的内部剖析器
- 定制剖析器(Parser)
- 实体转换
- 使用正则式处理糟糕的数据
- 玩玩
SoupStrainer
s - 通过剖析部分文档来提升效率
- 使用
extract
改进内存使用
- 其它
- 使用Beautiful Soup的其他应用
- 类似的库
- 小结
快速开始
从这里获得 Beautiful Soup。 变更日志 描述了3.0 版本与之前版本的不同。
在程序中中导入 Beautiful Soup库:
from BeautifulSoup import BeautifulSoup # For processing HTML from BeautifulSoup import BeautifulStoneSoup # For processing XML import BeautifulSoup # To get everything
下面的代码是Beautiful Soup基本功能的示范。你可以复制粘贴到你的python文件中,自己运行看看。
from BeautifulSoup import BeautifulSoup import re doc = ['<html><head><title>Page title</title></head>', '<body><p id="firstpara" align="center">This is paragraph <b>one</b>.', '<p id="secondpara" align="blah">This is paragraph <b>two</b>.', '</html>'] soup = BeautifulSoup(''.join(doc)) print soup.prettify() # <html> # <head> # <title> # Page title # </title> # </head> # <body> # <p id="firstpara" align="center"> # This is paragraph # <b> # one # </b> # . # </p> # <p id="secondpara" align="blah"> # This is paragraph # <b> # two # </b> # . # </p> # </body> # </html>
navigate soup的一些方法:
soup.contents[0].name # u'html' soup.contents[0].contents[0].name # u'head' head = soup.contents[0].contents[0] head.parent.name # u'html' head.next # <title>Page title</title> head.nextSibling.name # u'body' head.nextSibling.contents[0] # <p id="firstpara" align="center">This is paragraph <b>one</b>.</p> head.nextSibling.contents[0].nextSibling # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>
下面是一些方法搜索soup,获得特定标签或有着特定属性的标签:
titleTag = soup.html.head.title titleTag # <title>Page title</title> titleTag.string # u'Page title' len(soup('p')) # 2 soup.findAll('p', align="center") # [<p id="firstpara" align="center">This is paragraph <b>one</b>. </p>] soup.find('p', align="center") # <p id="firstpara" align="center">This is paragraph <b>one</b>. </p> soup('p', align="center")[0]['id'] # u'firstpara' soup.find('p', align=re.compile('^b.*'))['id'] # u'secondpara' soup.find('p').b.string # u'one' soup('p')[1].b.string # u'two'
修改soup也很简单:
titleTag['id'] = 'theTitle' titleTag.contents[0].replaceWith("New title") soup.html.head # <head><title id="theTitle">New title</title></head> soup.p.extract() soup.prettify() # <html> # <head> # <title id="theTitle"> # New title # </title> # </head> # <body> # <p id="secondpara" align="blah"> # This is paragraph # <b> # two # </b> # . # </p> # </body> # </html> soup.p.replaceWith(soup.b) # <html> # <head> # <title id="theTitle"> # New title # </title> # </head> # <body> # <b> # two # </b> # </body> # </html> soup.body.insert(0, "This page used to have ") soup.body.insert(2, " <p> tags!") soup.body # <body>This page used to have <b>two</b> <p> tags!</body>
一个实际例子,用于抓取 ICC Commercial Crime Services weekly piracy report页面, 使用Beautiful Soup剖析并获得发生的盗版事件:
import urllib2 from BeautifulSoup import BeautifulSoup page = urllib2.urlopen("http://www.icc-ccs.org/prc/piracyreport.php") soup = BeautifulSoup(page) for incident in soup('td', width="90%"): where, linebreak, what = incident.contents[:3] print where.strip() print what.strip() print
剖析文档
Beautiful Soup使用XML或HTML文档以字符串的方式(或类文件对象)构造。 它剖析文档并在内存中创建通讯的数据结构
如果你的文档格式是非常标准的,解析出来的数据结构正如你的原始文档。但是 如果你的文档有问题,Beautiful Soup会使用heuristics修复可能的结构问题。
剖析 HTML
使用 BeautifulSoup
类剖析HTML文档。 BeautifulSoup
会得出以下一些信息:
- 有些标签可以内嵌 (<BLOCKQUOTE>) ,有些不行 (<P>).
- table和list标签有一个自然的内嵌顺序。例如,<TD> 标签内为 <TR> 标签,而不会相反。
- <SCRIPT> 标签的内容不会被剖析为HTML。
- <META> 标签可以知道文档的编码类型。
这是运行例子:
from BeautifulSoup import BeautifulSoup html = "<html><p>Para 1<p>Para 2<blockquote>Quote 1<blockquote>Quote 2" soup = BeautifulSoup(html) print soup.prettify() # <html> # <p> # Para 1 # </p> # <p> # Para 2 # <blockquote> # Quote 1 # <blockquote> # Quote 2 # </blockquote> # </blockquote> # </p> # </html>
注意:BeautifulSoup
会智能判断那些需要添加关闭标签的位置,即使原始的文档没有。
也就是说那个文档不是一个有效的HTML,但是它也不是太糟糕。下面是一个比较糟糕的文档。 在一些问题中,它的<FORM>的开始在 <TABLE> 外面,结束在<TABLE>里面。 (这种HTML在一些大公司的页面上也屡见不鲜)
from BeautifulSoup import BeautifulSoup html = """ <html> <form> <table> <td><input name="input1">Row 1 cell 1 <tr><td>Row 2 cell 1 </form> <td>Row 2 cell 2<br>This</br> sure is a long cell </body> </html>"""
Beautiful Soup 也可以处理这个文档:
print BeautifulSoup(html).prettify() # <html> # <form> # <table> # <td> # <input name="input1" /> # Row 1 cell 1 # </td> # <tr> # <td> # Row 2 cell 1 # </td> # </tr> # </table> # </form> # <td> # Row 2 cell 2 # <br /> # This # sure is a long cell # </td> # </html>
table的最后一个单元格已经在标签<TABLE>外了;Beautiful Soup 决定关闭<TABLE>标签当它在<FORM>标签哪里关闭了。 写这个文档家伙原本打算使用<FORM>标签扩展到table的结尾,但是Beautiful Soup 肯定不知道这些。即使遇到这样糟糕的情况, Beautiful Soup 仍可以剖析这个不合格文档,使你开业存取所有数据。
剖析 XML
BeautifulSoup
类似浏览器,是个具有启发性的类,可以尽可能的推测HTML文档作者的意图。 但是XML没有固定的标签集合,因此这些启发式的功能没有作用。因此BeautifulSoup
处理XML不是很好。
使用BeautifulStoneSoup
类剖析XML文档。它是一个 概括的类,没有任何特定的XML方言已经简单的标签内嵌规则。 下面是范例:
from BeautifulSoup import BeautifulStoneSoup xml = "<doc><tag1>Contents 1<tag2>Contents 2<tag1>Contents 3" soup = BeautifulStoneSoup(xml) print soup.prettify() # <doc> # <tag1> # Contents 1 # <tag2> # Contents 2 # </tag2> # </tag1> # <tag1> # Contents 3 # </tag1> # </doc>
BeautifulStoneSoup
的一个主要缺点就是它不知道如何处理自结束标签 。 HTML 有固定的自结束标签集合,但是XML取决对应的DTD文件。你可以通过传递selfClosingTags
的参数的名字到 BeautifulStoneSoup
的构造器中,指定自结束标签:
from BeautifulSoup import BeautifulStoneSoup xml = "<tag>Text 1<selfclosing>Text 2" print BeautifulStoneSoup(xml).prettify() # <tag> # Text 1 # <selfclosing> # Text 2 # </selfclosing> # </tag> print BeautifulStoneSoup(xml, selfClosingTags=['selfclosing']).prettify() # <tag> # Text 1 # <selfclosing /> # Text 2 # </tag>
如果它不工作
这里有 一些其他的剖析类 使用与上述两个类不同的智能感应。 你也可以子类化以及定制一个剖析器 使用你自己的智能感应方法。
使用Unicode的Beautiful Soup,Dammit
当你的文档被剖析之后,它就自动被转换为unicode。 Beautiful Soup 只存储Unicode字符串。
from BeautifulSoup import BeautifulSoup soup = BeautifulSoup("Hello") soup.contents[0] # u'Hello' soup.originalEncoding # 'ascii'
使用UTF-8编码的日文文档例子:
from BeautifulSoup import BeautifulSoup soup = BeautifulSoup("\xe3\x81\x93\xe3\x82\x8c\xe3\x81\xaf") soup.contents[0] # u'\こ\れ\は' soup.originalEncoding # 'utf-8' str(soup) # '\xe3\x81\x93\xe3\x82\x8c\xe3\x81\xaf' # Note: this bit uses EUC-JP, so it only works if you have cjkcodecs # installed, or are running Python 2.4. soup.__str__('euc-jp') # '\xa4\xb3\xa4\xec\xa4\xcf'
Beautiful Soup 使用一个称为UnicodeDammit
的类去来检测文档的编码,并将其转换为Unicode。 如果你需要为其他文档(没有石油Beautiful Soup剖析过得文档)使用这转换,你也可以 直接使用UnicodeDammit
。 它是基于Universal Feed Parser开发的。
如果你使用Python2.4之前的版本,请下载和安装cjkcodecs
以及iconvcodec
是python支持更多的编码,特别是CJK编码。要想更好地自动检测, 你也要安装chardet
Beautiful Soup 会按顺序尝试不同的编码将你的文档转换为Unicode:
- 可以通过
fromEncoding
参数传递编码类型给soup的构造器 - 通过文档本身找到编码类型:例如XML的声明或者HTML文档
http-equiv
的META标签。 如果Beautiful Soup在文档中发现编码类型,它试着使用找到的类型转换文档。 但是,如果你明显的指定一个编码类型, 并且成功使用了编码:这时它会忽略任何它在文档中发现的编码类型。 - 通过嗅探文件开头的一下数据,判断编码。如果编码类型可以被检测到, 它将是这些中的一个:UTF-*编码,EBCDIC或者ASCII。
- 通过
chardet
库,嗅探编码,如果你安装了这个库。 - UTF-8
- Windows-1252
Beautiful Soup总是会猜对它可以猜测的。但是对于那些没有声明以及有着奇怪编码 的文档,它会常常会失败。这时,它会选择Windows-1252编码,这个可能是错误的编码。 下面是EUC-JP的例子,Beautiful Soup猜错了编码。(重申一下:因为它使用了EUC-JP, 这个例子只会在 python 2.4或者你安装了cjkcodecs
的情况下才工作。):
from BeautifulSoup import BeautifulSoup euc_jp = '\xa4\xb3\xa4\xec\xa4\xcf' soup = BeautifulSoup(euc_jp) soup.originalEncoding # 'windows-1252' str(soup) # '\xc2\xa4\xc2\xb3\xc2\xa4\xc3\xac\xc2\xa4\xc3\x8f' # Wrong!
但如果你使用fromEncoding
参数指定编码, 它可以正确的剖析文档,并可以将文档转换为UTF-8或者转回EUC-JP。
soup = BeautifulSoup(euc_jp, fromEncoding="euc-jp") soup.originalEncoding # 'windows-1252' str(soup) # '\xe3\x81\x93\xe3\x82\x8c\xe3\x81\xaf' # Right! soup.__str__(self, 'euc-jp') == euc_jp # True
如果你指定Beautiful Soup使用 Windows-1252编码(或者类似的编码如ISO-8859-1,ISO-8859-2), Beautiful Soup会找到并破坏文档的smart quotes以及其他的Windows-specific 字符。 这些字符不会转换为相应的Unicode,而是将它们变为HTML entities(BeautifulSoup
) 或者XML entitis(BeautifulStoneSoup
)。
但是,你可以指定参数smartQuotesTo=None
到soup构造器:这时 smart quotes会被正确的转换为Unicode。你也可以指定smartQuotesTo
为"xml"或"html" 去改变BeautifulSoup
和BeautifulStoneSoup
的默认操作。
from BeautifulSoup import BeautifulSoup, BeautifulStoneSoup text = "Deploy the \x91SMART QUOTES\x92!" str(BeautifulSoup(text)) # 'Deploy the ‘SMART QUOTES’!' str(BeautifulStoneSoup(text)) # 'Deploy the ‘SMART QUOTES’!' str(BeautifulSoup(text, smartQuotesTo="xml")) # 'Deploy the ‘SMART QUOTES’!' BeautifulSoup(text, smartQuotesTo=None).contents[0] # u'Deploy the \‘SMART QUOTES\’!'
输出文档
你可以使用 str
函数将Beautiful Soup文档(或者它的子集)转换为字符串, 或者使用它的code>prettify或renderContents
。 你也可以使用unicode
函数以Unicode字符串的形式获得。
prettify
方法添加了一些换行和空格以便让文档结构看起来更清晰。 它也将那些只包含空白符的,可能影响一个XML文档意义的文档节点(nodes)剔除(strips out)。str
和unicode
函数不会剔除这些节点,他们也不会添加任何空白符。
看看这个例子:
from BeautifulSoup import BeautifulSoup doc = "<html><h1>Heading</h1><p>Text" soup = BeautifulSoup(doc) str(soup) # '<html><h1>Heading</h1><p>Text</p></html>' soup.renderContents() # '<html><h1>Heading</h1><p>Text</p></html>' soup.__str__() # '<html><h1>Heading</h1><p>Text</p></html>' unicode(soup) # u'<html><h1>Heading</h1><p>Text</p></html>' soup.prettify() # '<html>\n <h1>\n Heading\n </h1>\n <p>\n Text\n </p>\n</html>' print soup.prettify() # <html> # <h1> # Heading # </h1> # <p> # Text # </p> # </html>
可以看到使用文档中的tag成员时 str
和renderContents
返回的结果是不同的。
heading = soup.h1 str(heading) # '<h1>Heading</h1>' heading.renderContents() # 'Heading'
当你调用__str__
,prettify
或者renderContents
时, 你可以指定输出的编码。默认的编码(str
使用的)是UTF-8。 下面是处理ISO-8851-1的串并以不同的编码输出同样的串的例子。
from BeautifulSoup import BeautifulSoup doc = "Sacr\xe9 bleu!" soup = BeautifulSoup(doc) str(soup) # 'Sacr\xc3\xa9 bleu!' # UTF-8 soup.__str__("ISO-8859-1") # 'Sacr\xe9 bleu!' soup.__str__("UTF-16") # '\xff\xfeS\x00a\x00c\x00r\x00\xe9\x00 \x00b\x00l\x00e\x00u\x00!\x00' soup.__str__("EUC-JP") # 'Sacr\x8f\xab\xb1 bleu!'
如果原始文档含有编码声明,Beautiful Soup会将原始的编码声明改为新的编码。 也就是说,你载入一个HTML文档到BeautifulSoup
后,在输出它,不仅HTML被清理 过了,而且可以明显的看到它已经被转换为UTF-8。
这是HTML的例子:
from BeautifulSoup import BeautifulSoup doc = """<html> <meta http-equiv="Content-type" content="text/html; charset=ISO-Latin-1" > Sacr\xe9 bleu! </html>""" print BeautifulSoup(doc).prettify() # <html> # <meta http-equiv="Content-type" content="text/html; charset=utf-8" /> # Sacré bleu! # </html>
这是XML的例子:
from BeautifulSoup import BeautifulStoneSoup doc = """<?xml version="1.0" encoding="ISO-Latin-1">Sacr\xe9 bleu!""" print BeautifulStoneSoup(doc).prettify() # <?xml version='1.0' encoding='utf-8'> # Sacré bleu!
剖析树
到目前为止,我们只是载入文档,然后再输出它。 现在看看更让我们感兴趣的剖析树: Beautiful Soup剖析一个文档后生成的数据结构。
剖析对象 (BeautifulSoup
或 BeautifulStoneSoup
的实例)是深层嵌套(deeply-nested), 精心构思的(well-connected)的数据结构,可以与XML和HTML结构相互协调。 剖析对象包括2个其他类型的对象,Tag
对象, 用于操纵像<TITLE> ,<B>这样的标签;NavigableString
对象, 用于操纵字符串,如"Page title"和"This is paragraph"。
NavigableString
的一些子类 (CData
, Comment
, Declaration
, and ProcessingInstruction
), 也处理特殊XML结构。 它们就像NavigableString
一样, 除了但他们被输出时, 他们会被添加一些额外的数据。下面是一个包含有注释(comment)的文档:
from BeautifulSoup import BeautifulSoup import re hello = "Hello! <!--I've got to be nice to get what I want.-->" commentSoup = BeautifulSoup(hello) comment = commentSoup.find(text=re.compile("nice")) comment.__class__ # <class 'BeautifulSoup.Comment'> comment # u"I've got to be nice to get what I want." comment.previousSibling # u'Hello! ' str(comment) # "<!--I've got to be nice to get what I want.-->" print commentSoup # Hello! <!--I've got to be nice to get what I want.-->
现在,我们深入研究一下我们开头使用的那个文档:
from BeautifulSoup import BeautifulSoup doc = ['<html><head><title>Page title</title></head>', '<body><p id="firstpara" align="center">This is paragraph <b>one</b>.', '<p id="secondpara" align="blah">This is paragraph <b>two</b>.', '</html>'] soup = BeautifulSoup(''.join(doc)) print soup.prettify() # <html> # <head> # <title> # Page title # </title> # </head> # <body> # <p id="firstpara" align="center"> # This is paragraph # <b> # one # </b> # . # </p> # <p id="secondpara" align="blah"> # This is paragraph # <b> # two # </b> # . # </p> # </body> # </html>
Tag
的属性
Tag
和NavigableString
对象有很多有用的成员,在 Navigating剖析树和 Searching剖析树中我们会更详细的介绍。 现在,我们先看看这里使用的Tag
成员:属性
SGML标签有属性:.例如,在上面那个HTML 中每个<P>标签都有"id"属性和"align"属性。 你可以将Tag
看成字典来访问标签的属性:
firstPTag, secondPTag = soup.findAll('p') firstPTag['id'] # u'firstPara' secondPTag['id'] # u'secondPara'
NavigableString
对象没有属性;只有Tag
对象有属性。
Navigating剖析树
Tag
对象都有如下含有所有的成员的列表(尽管,某些实际的成员值可能为None
). NavigableString
对象也有下面这些成员,除了contents
和 string
成员。
parent
上面那个 例子中, <HEAD> Tag
的parent是<HTML> Tag
. <HTML> Tag
的parent是BeautifulSoup
剖析对象自己。 剖析对象的parent是None
. 利用parent
,你可以向前遍历剖析树。
soup.head.parent.name # u'html' soup.head.parent.parent.__class__.__name__ # 'BeautifulSoup' soup.parent == None # True
contents
使用parent
向前遍历树。使用contents
向后遍历树。 contents
是Tag
的有序列表, NavigableString
对象包含在一个页面元素内。只有最高层的剖析对象和 Tag
对象有contents
。NavigableString
只有strings,不能包含子元素,因此他们也没有contents
.
在上面的例子中, contents
的第一个<P> Tag
是个列表,包含一个 NavigableString
("This is paragraph "), 一个<B> Tag
, 和其它的 NavigableString
(".")。而contents
的<B>Tag
: 包含一个NavigableString
("one")的列表。
pTag = soup.p pTag.contents # [u'This is paragraph ', <b>one</b>, u'.'] pTag.contents[1].contents # [u'one'] pTag.contents[0].contents # AttributeError: 'NavigableString' object has no attribute 'contents'
string
为了方便,如果一个标签只有一个子节点且是字符串类型,这个自己可以这样访问 tag.string
,等同于tag.contents[0]
的形式。 在上面的例子中, soup.b.string
是个NavigableString
对象,它的值是Unicode字符串"one". 这是剖析树中<B>Tag
的第一个string。
soup.b.string # u'one' soup.b.contents[0] # u'one'
但是soup.p.string
是None
, 剖析中的第一个<P> Tag
拥有多个子元素。soup.head.string
也为None
, 虽然<HEAD> Tag只有一个子节点,但是这个子节点是Tag
类型 (<TITLE> Tag
), 不是NavigableString
。
soup.p.string == None # True soup.head.string == None # True
nextSibling
和previousSibling
使用它们你可以跳往在剖析树中同等层次的下一个元素。 在上面的文档中, <HEAD> Tag
的nextSibling
是<BODY> Tag
, 因为<BODY> Tag
是在<html> Tag
的下一层。 <BODY>标签的nextSibling
为None
, 因为<HTML>下一层没有标签是直接的在它之后。
soup.head.nextSibling.name # u'body' soup.html.nextSibling == None # True
相应的<BODY> Tag
的previousSibling
是<HEAD>标签, <HEAD> Tag
的previousSibling
为None
:
soup.body.previousSibling.name # u'head' soup.head.previousSibling == None # True
更多例子:<P> Tag
的第一个nextSibling
是第二个 <P> Tag
。 第二个<P>Tag
里的<B>
Tag
的previousSibling
是 NavigableString
"This is paragraph"。 这个NavigableString
的previousSibling
是None
, 不会是第一个<P> Tag
里面的任何元素。
soup.p.nextSibling # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p> secondBTag = soup.findAlll('b')[1] secondBTag.previousSibling # u'This is paragraph' secondBTag.previousSibling.previousSibling == None # True
next
和previous
使用它们可以按照soup处理文档的次序遍历整个文档,而不是它们在剖析树中看到那种次序。 例如<HEAD> Tag
的next
是<TITLE>Tag
, 而不是<BODY> Tag
。 这是因为在原始文档中,<TITLE> tag 直接在<HEAD>标签之后。
soup.head.next # u'title' soup.head.nextSibling.name # u'body' soup.head.previous.name # u'html'
Where next
and previous
are concerned, a Tag
's contents
come before its nextSibling
. 通常不会用到这些成员,但有时使用它们能够非常方便地从剖析树获得不易找到的信息。
遍历一个标签(Iterating over a Tag)
你可以像遍历list一样遍历一个标签(Tag
)的contents
。 这非常有用。类似的,一个Tag的有多少child可以直接使用len(tag)而不必使用len(tag.contents)来获得。 以上面那个文档中的为例:
for i in soup.body: print i # <p id="firstpara" align="center">This is paragraph <b>one</b>.</p> # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p> len(soup.body) # 2 len(soup.body.contents) # 2
使用标签(tag)名作为成员
像剖析对象或Tag对象的成员一样使用Tag名可以很方便的操作剖析树。 前面一些例子我们已经用到了这种方式。以上述文档为例, soup.head
获得文档第一个<HEAD>标签:
soup.head # <head><title>Page title</title></head>
通常,调用mytag.foo
获得的是mytag
的第一个child,同时必须是一个<FOO> 标签
。 如果在mytag
中没有<FOO> 标签
,mytag.foo
返回一个None
。 你可以使用这中方法快速的读取剖析树:
soup.head.title # <title>Page title</title> soup.body.p.b.string # u'one'
你也可以使用这种方法快速的跳到剖析树的某个特定位置。例如,如果你担心<TITLE> tags会离奇的在<HEAD> tag之外, 你可以使用soup.title
去获得一个HTML文档的标题(title),而不必使用soup.head.title
:
soup.title.string # u'Page title'
soup.p
跳到文档中的第一个 <P> tag,不论它在哪里。 soup.table.tr.td
跳到文档总第一个table的第一列第一行。
这些成员实际上是下面first
方法的别名,这里更多介绍。 这里提到是因为别名使得一个定位(zoom)一个结构良好剖析树变得异常容易。
获得第一个<FOO> 标签另一种方式是使用.fooTag
而不是 .foo
。 例如,soup.table.tr.td
可以表示为soup.tableTag.trTag.tdTag
,甚至为soup.tableTag.tr.tdTag
。 如果你喜欢更明确的知道表示的意义,或者你在剖析一个标签与Beautiful Soup的方法或成员有冲突的XML文档是,使用这种方式非常有用。
from BeautifulSoup import BeautifulStoneSoup xml = '<person name="Bob"><parent rel="mother" name="Alice">' xmlSoup = BeautifulStoneSoup(xml) xmlSoup.person.parent # A Beautiful Soup member # <person name="Bob"><parent rel="mother" name="Alice"></parent></person> xmlSoup.person.parentTag # A tag name # <parent rel="mother" name="Alice"></parent>
如果你要找的标签名不是有效的Python标识符,(例如hyphenated-name
),你就需要使用first
方法了。
搜索剖析树
Beautiful Soup提供了许多方法用于浏览一个剖析树,收集你指定的Tag
和NavigableString
。
有几种方法去定义用于Beautiful Soup的匹配项。 我们先用深入解释最基本的一种搜索方法findAll
。 和前面一样,我们使用下面这个文档说明:
from BeautifulSoup import BeautifulSoup doc = ['<html><head><title>Page title</title></head>', '<body><p id="firstpara" align="center">This is paragraph <b>one</b>.', '<p id="secondpara" align="blah">This is paragraph <b>two</b>.', '</html>'] soup = BeautifulSoup(''.join(doc)) print soup.prettify() # <html> # <head> # <title> # Page title # </title> # </head> # <body> # <p id="firstpara" align="center"> # This is paragraph # <b> # one # </b> # . # </p> # <p id="secondpara" align="blah"> # This is paragraph # <b> # two # </b> # . # </p> # </body> # </html>
还有, 这里的两个方法(findAll
和 find
)仅对Tag
对象以及 顶层剖析对象有效,但 NavigableString
不可用。 这两个方法在Searching 剖析树内部同样可用。
The basic find method: findAll(
name, attrs, recursive, text, limit, **kwargs)
方法findAll
从给定的点开始遍历整个树,并找到满足给定条件所有Tag
以及NavigableString
。 findall
函数原型定义如下:
findAll(name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)
这些参数会反复的在这个文档中出现。其中最重要的是name
参数 和keywords参数(译注:就是**kwargs参数)。
参数
name
匹配tags的名字,获得相应的结果集。 有几种方法去匹配name,在这个文档中会一再的用到。最简单用法是仅仅给定一个tag name值。下面的代码寻找文档中所有的 <B>
Tag
:soup.findAll('b') # [<b>one</b>, <b>two</b>]
你可以传一个正则表达式。下面的代码寻找所有以b开头的标签:
import re tagsStartingWithB = soup.findAll(re.compile('^b')) [tag.name for tag in tagsStartingWithB] # [u'body', u'b', u'b']
你可以传一个list或dictionary。下面两个调用是查找所有的<TITLE>和<P>标签。 他们获得结果一样,但是后一种方法更快一些:
soup.findAll(['title', 'p']) # [<title>Page title</title>, # <p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>] soup.findAll({'title' : True, 'p' : True}) # [<title>Page title</title>, # <p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>]
你可以传一个
True
值,这样可以匹配每个tag的name:也就是匹配每个tag。allTags = soup.findAll(True) [tag.name for tag in allTags] [u'html', u'head', u'title', u'body', u'p', u'b', u'p', u'b']
这看起来不是很有用,但是当你限定属性(attribute)值时候,使用
True
就很有用了。你可以传callable对象,就是一个使用
Tag
对象作为它唯一的参数,并返回布尔值的对象。findAll
使用的每个作为参数的Tag
对象都会传递给这个callable对象, 并且如果调用返回True
,则这个tag便是匹配的。下面是查找两个并仅两个属性的标签(tags):
soup.findAll(lambda tag: len(tag.attrs) == 2) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>]
下面是寻找单个字符为标签名并且没有属性的标签:
soup.findAll(lambda tag: len(tag.name) == 1 and not tag.attrs) # [<b>one</b>, <b>two</b>]
keyword参数用于筛选tag的属性。下面这个例子是查找拥有属性align且值为center的 所有标签:
soup.findAll(align="center") # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>]
如同
name
参数,你也可以使用不同的keyword参数对象,从而更加灵活的指定属性值的匹配条件。 你可以向上面那样传递一个字符串,来匹配属性的值。你也可以传递一个正则表达式,一个列表(list),一个哈希表(hash), 特殊值True
或None
,或者一个可调用的以属性值为参数的对象(注意:这个值可能为None
)。 一些例子:soup.findAll(id=re.compile("para$")) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>] soup.findAll(align=["center", "blah"]) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>] soup.findAll(align=lambda(value): value and len(value) < 5) # [<p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>]
特殊值
True
和None
更让人感兴趣。True
匹配给定属性为任意值的标签,None
匹配那些给定的属性值为空的标签。 一些例子如下:soup.findAll(align=True) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>] [tag.name for tag in soup.findAll(align=None)] # [u'html', u'head', u'title', u'body', u'b', u'b']
如果你需要在标签的属性上添加更加复杂或相互关联的(interlocking)匹配值, 如同上面一样,以callable对象的传递参数来处理
Tag
对象。在这里你也许注意到一个问题。 如果你有一个文档,它有一个标签定义了一个name属性,会怎么样? 你不能使用
name
为keyword参数,因为Beautiful Soup 已经定义了一个name
参数使用。 你也不能用一个Python的保留字例如for
作为关键字参数。Beautiful Soup提供了一个特殊的参数
attrs
,你可以使用它来应付这些情况。attrs
是一个字典,用起来就和keyword参数一样:soup.findAll(id=re.compile("para$")) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>] soup.findAll(attrs={'id' : re.compile("para$")}) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>]
你可以使用
attrs
去匹配那些名字为Python保留字的属性, 例如class
,for
, 以及import
; 或者那些不是keyword参数但是名字为Beautiful Soup搜索方法使用的参数名的属性, 例如name
,recursive
,limit
,text
, 以及attrs
本身。from BeautifulSoup import BeautifulStoneSoup xml = '<person name="Bob"><parent rel="mother" name="Alice">' xmlSoup = BeautifulStoneSoup(xml) xmlSoup.findAll(name="Alice") # [] xmlSoup.findAll(attrs={"name" : "Alice"}) # [parent rel="mother" name="Alice"></parent>]
使用CSS类查找
对于CSS类attrs
参数更加方便。例如class不仅是一个CSS属性, 也是Python的保留字。你可以使用
soup.find("tagName", { "class" : "cssClass" })
搜索CSS class,但是由于有很多这样的操作, 你也可以只传递一个字符串给attrs
。 这个字符串默认处理为CSS的class的参数值。from BeautifulSoup import BeautifulSoup soup = BeautifulSoup("""Bob's <b>Bold</b> Barbeque Sauce now available in <b class="hickory">Hickory</b> and <b class="lime">Lime</a>""") soup.find("b", { "class" : "lime" }) # <b class="lime">Lime</b> soup.find("b", "hickory") # <b class="hickory">Hickory</b>
text
是一个用于搜索NavigableString
对象的参数。 它的值可以是字符串,一个正则表达式, 一个list或dictionary,True
或None
, 一个以NavigableString
为参数的可调用对象:soup.findAll(text="one") # [u'one'] soup.findAll(text=u'one') # [u'one'] soup.findAll(text=["one", "two"]) # [u'one', u'two'] soup.findAll(text=re.compile("paragraph")) # [u'This is paragraph ', u'This is paragraph '] soup.findAll(text=True) # [u'Page title', u'This is paragraph ', u'one', u'.', u'This is paragraph ', # u'two', u'.'] soup.findAll(text=lambda(x): len(x) < 12) # [u'Page title', u'one', u'.', u'two', u'.']
如果你使用
text
,任何指定给name
以及keyword参数的值都会被忽略。recursive
是一个布尔参数(默认为True
),用于指定Beautiful Soup遍历整个剖析树, 还是只查找当前的子标签或者剖析对象。下面是这两种方法的区别:[tag.name for tag in soup.html.findAll()] # [u'head', u'title', u'body', u'p', u'b', u'p', u'b'] [tag.name for tag in soup.html.findAll(recursive=False)] # [u'head', u'body']
当
recursive
为false,只有当前的子标签<HTML>会被搜索。如果你需要搜索树, 使用这种方法可以节省一些时间。设置
limit
参数可以让Beautiful Soup 在找到特定个数的匹配时停止搜索。 文档中如果有上千个表格,但你只需要前四个,传值4到limit
可以让你节省很多时间。 默认是没有限制(limit没有指定值).soup.findAll('p', limit=1) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>] soup.findAll('p', limit=100) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>]
像findall
一样调用tag
一个小捷径。如果你像函数一样调用剖析对象或者Tag
对象, 这样你调用所用参数都会传递给findall
的参数,就和调用findall
一样。 就上面那个文档为例:
soup(text=lambda(x): len(x) < 12) # [u'Page title', u'one', u'.', u'two', u'.'] soup.body('p', limit=1) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>]
find(name, attrs, recursive, text, **kwargs)
好了,我们现在看看其他的搜索方法。他们都是有和 findAll
几乎一样的参数。
find
方法是最接近findAll
的函数, 只是它并不会获得所有的匹配对象,它仅仅返回找到第一个可匹配对象。 也就是说,它相当于limit
参数为1的结果集。 以上面的 文档为例:
soup.findAll('p', limit=1) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>] soup.find('p', limit=1) # <p id="firstpara" align="center">This is paragraph <b>one</b>.</p> soup.find('nosuchtag', limit=1) == None # True
通常,当你看到一个搜索方法的名字由复数构成 (如findAll
和findNextSiblings
)时, 这个方法就会存在limit
参数,并返回一个list的结果。但你 看到的方法不是复数形式(如find
和findNextSibling
)时, 你就可以知道这函数没有limit参数且返回值是单一的结果。
first
哪里去了?
早期的Beautiful Soup 版本有一些first
,fetch
以及fetchPrevious
方法。 这些方法还在,但是已经被弃用了,也许不久就不在存在了。 因为这些名字有些令人迷惑。新的名字更加有意义: 前面提到了,复数名称的方法名,比如含有All
的方法名,它将返回一个 多对象。否则,它只会返回单个对象。
Searching Within the Parse Tree
上面说明的方法findAll
及find
,都是从剖析树的某一点开始并一直往下。 他们反复的遍历对象的contents
直到最低点。
也就是说你不能在 NavigableString
对象上使用这些方法, 因为NavigableString
没有contents:它们是剖析树的叶子。
[这段翻译的不太准确]但是向下搜索不是唯一的遍历剖析树的方法。在Navigating剖析树 中,我们可以使用这些方法:parent
, nextSibling
等。 他们都有2个相应的方法:一个类似findAll
,一个类似find
. 由于NavigableString
对象也支持这些方法,你可以像Tag
一样 使用这些方法。
为什么这个很有用?因为有些时候,你不能使用findAll
或find
从Tag
或NavigableString
获得你想要的。例如,下面的HTML文档:
from BeautifulSoup import BeautifulSoup soup = BeautifulSoup('''<ul> <li>An unrelated list </ul> <h1>Heading</h1> <p>This is <b>the list you want</b>:</p> <ul><li>The data you want</ul>''')
有很多方法去定位到包含特定数据的<LI> 标签。最明显的方式如下:
soup('li', limit=2)[1] # <li>The data you want</li>
显然,这样获得所需的<LI>标签并不稳定。如果,你只分析一次页面,这没什么影响。 但是如果你需要在一段时间分析很多次这个页面,就需要考虑一下这种方法。 If the irrelevant list grows another <LI> tag, you'll get that tag instead of the one you want, and your script will break or give the wrong data.
因为如果列表发生变化,你可能就得不到你想要的结果。
soup('ul', limit=2)[1].li # <li>The data you want</li>
That's is a little better, because it can survive changes to the irrelevant list. But if the document grows another irrelevant list at the top, you'll get the first <LI> tag of that list instead of the one you want. A more reliable way of referring to the ul tag you want would better reflect that tag's place in the structure of the document.
这有一点好处,因为那些不相干的列表的变更生效了。 但是如果文档增长的不相干的列表在顶部,你会获得第一个<LI>标签而不是 你想要的标签。一个更可靠的方式是去引用对应的ul标签, 这样可以更好的处理文档的结构。
在HTML里面,你也许认为你想要的list是<H1>标签下的<UL>标签。 问题是那个标签不是在<H1>下,它只是在它后面。获得<H1>标签很容易,但是获得 <UL>却没法使用first
和fetch
, 因为这些方法只是搜索<H1>标签的contents
。 你需要使用next
或nextSibling
来获得<UL>标签。
s = soup.h1 while getattr(s, 'name', None) != 'ul': s = s.nextSibling s.li # <li>The data you want</li>
或者,你觉得这样也许会比较稳定:
s = soup.find(text='Heading') while getattr(s, 'name', None) != 'ul': s = s.next s.li # <li>The data you want</li>
但是还有很多困难需要你去克服。这里会介绍一下非常有用的方法。 你可以在你需要的使用它们写一些遍历成员的方法。它们以某种方式遍历树,并跟踪那些满足条件的Tag
和NavigableString
对象。代替上面那个例子的第一的循环的代码,你可以这样写:
soup.h1.findNextSibling('ul').li # <li>The data you want</li>
第二循环,你可以这样写:
soup.find(text='Heading').findNext('ul').li # <li>The data you want</li>
这些循环代替调用findNextString
和findNext
。 本节剩下的内容是这种类型所用方法的参考。同时,对于遍历总是有两种方法: 一个是返回list的findAll
,一个是返回单一量的find
。
下面,我们再举一个例子来说明:
from BeautifulSoup import BeautifulSoup doc = ['<html><head><title>Page title</title></head>', '<body><p id="firstpara" align="center">This is paragraph <b>one</b>.', '<p id="secondpara" align="blah">This is paragraph <b>two</b>.', '</html>'] soup = BeautifulSoup(''.join(doc)) print soup.prettify() # <html> # <head> # <title> # Page title # </title> # </head> # <body> # <p id="firstpara" align="center"> # This is paragraph # <b> # one # </b> # . # </p> # <p id="secondpara" align="blah"> # This is paragraph # <b> # two # </b> # . # </p> # </body> # </html>
findNextSiblings(name, attrs, text, limit, **kwargs)
and findNextSibling(name, attrs, text, **kwargs)
这两个方法以nextSibling
的成员为依据, 获得满足条件的Tag
或NavigableText
对象。 以上面的文档为例:
paraText = soup.find(text='This is paragraph ') paraText.findNextSiblings('b') # [<b>one</b>] paraText.findNextSibling(text = lambda(text): len(text) == 1) # u'.'
findPreviousSiblings(name, attrs, text, limit, **kwargs)
and findPreviousSibling(name, attrs, text, **kwargs)
这两个方法以previousSibling
成员为依据,获得满足条件的Tag
和 NavigableText
对象。 以上面的文档为例:
paraText = soup.find(text='.') paraText.findPreviousSiblings('b') # [<b>one</b>] paraText.findPreviousSibling(text = True) # u'This is paragraph '
findAllNext(name, attrs, text, limit, **kwargs)
and findNext(name, attrs, text, **kwargs)
这两个方法以next
的成员为依据, 获得满足条件的Tag
和NavigableText
对象。 以上面的文档为例:
pTag = soup.find('p') pTag.findAllNext(text=True) # [u'This is paragraph ', u'one', u'.', u'This is paragraph ', u'two', u'.'] pTag.findNext('p') # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p> pTag.findNext('b') # <b>one</b>
findAllPrevious(name, attrs, text, limit, **kwargs)
and findPrevious(name, attrs, text, **kwargs)
这两方法以previous
的成员依据, 获得满足条件的Tag
和NavigableText
对象。 以上面的文档为例:
lastPTag = soup('p')[-1] lastPTag.findAllPrevious(text=True) # [u'.', u'one', u'This is paragraph ', u'Page title'] # Note the reverse order! lastPTag.findPrevious('p') # <p id="firstpara" align="center">This is paragraph <b>one</b>.</p> lastPTag.findPrevious('b') # <b>one</b>
findParents(name, attrs, limit, **kwargs)
and findParent(name, attrs, **kwargs)
这两个方法以parent
成员为依据, 获得满足条件的Tag
和NavigableText
对象。 他们没有text
参数,因为这里的对象的parent不会有NavigableString
。 以上面的文档为例:
bTag = soup.find('b') [tag.name for tag in bTag.findParents()] # [u'p', u'body', u'html', '[document]'] # NOTE: "u'[document]'" means that that the parser object itself matched. bTag.findParent('body').name # u'body'
修改剖析树
现在你已经知道如何在剖析树中寻找东西了。但也许你想对它做些修改并输出出来。 你可以仅仅将一个元素从其父母的contents
中分离,但是文档的其他部分仍然 拥有对这个元素的引用。Beautiful Soup 提供了几种方法帮助你修改剖析树并保持其内部的一致性。
修改属性值
你可以使用字典赋值来修改Tag
对象的属性值。
from BeautifulSoup import BeautifulSoup soup = BeautifulSoup("<b id="2">Argh!</b>") print soup # <b id="2">Argh!</b> b = soup.b b['id'] = 10 print soup # <b id="10">Argh!</b> b['id'] = "ten" print soup # <b id="ten">Argh!</b> b['id'] = 'one "million"' print soup # <b id='one "million"'>Argh!</b>
你也可以删除一个属性值,然后添加一个新的属性:
del(b['id']) print soup # <b>Argh!</b> b['class'] = "extra bold and brassy!" print soup # <b class="extra bold and brassy!">Argh!</b>
删除元素
要是你引用了一个元素,你可以使用extract
将它从树中抽离。 下面是将所有的注释从文档中移除的代码:
from BeautifulSoup import BeautifulSoup, Comment soup = BeautifulSoup("""1<!--The loneliest number--> <a>2<!--Can be as bad as one--><b>3""") comments = soup.findAll(text=lambda text:isinstance(text, Comment)) [comment.extract() for comment in comments] print soup # 1 # <a>2<b>3</b></a>
这段代码是从文档中移除一个子树:
from BeautifulSoup import BeautifulSoup soup = BeautifulSoup("<a1></a1><a><b>Amazing content<c><d></a><a2></a2>") soup.a1.nextSibling # <a><b>Amazing content<c><d></d></c></b></a> soup.a2.previousSibling # <a><b>Amazing content<c><d></d></c></b></a> subtree = soup.a subtree.extract() print soup # <a1></a1><a2></a2> soup.a1.nextSibling # <a2></a2> soup.a2.previousSibling # <a1></a1>
extract
方法将一个剖析树分离为两个不连贯的树。naviation的成员也因此变得看起来好像这两个树 从来不是一起的。
soup.a1.nextSibling # <a2></a2> soup.a2.previousSibling # <a1></a1> subtree.previousSibling == None # True subtree.parent == None # True
使用一个元素替换另一个元素
replaceWith
方法抽出一个页面元素并将其替换为一个不同的元素。 新元素可以为一个Tag
(它可能包含一个剖析树)或者NavigableString
。 如果你传一个字符串到replaceWith
, 它会变为NavigableString
。 这个Navigation成员会完全融入到这个剖析树中,就像它本来就存在一样。
下面是一个简单的例子:
The new element can be aTag
(possibly with a whole parse tree beneath it) or a NavigableString
. If you pass a plain old string into replaceWith
, it gets turned into aNavigableString
. The navigation members are changed as though the document had been parsed that way in the first place.Here's a simple example:
from BeautifulSoup import BeautifulSoup soup = BeautifulSoup("<b>Argh!</b>") soup.find(text="Argh!").replaceWith("Hooray!") print soup # <b>Hooray!</b> newText = soup.find(text="Hooray!") newText.previous # <b>Hooray!</b> newText.previous.next # u'Hooray!' newText.parent # <b>Hooray!</b> soup.b.contents # [u'Hooray!']
这里有一个更复杂点的,相互替换标签(tag)的例子:
from BeautifulSoup import BeautifulSoup, Tag soup = BeautifulSoup("<b>Argh!<a>Foo</a></b><i>Blah!</i>") tag = Tag(soup, "newTag", [("id", 1)]) tag.insert(0, "Hooray!") soup.a.replaceWith(tag) print soup # <b>Argh!<newTag id="1">Hooray!</newTag></b><i>Blah!</i>
You can even rip out an element from one part of the document and stick it in another part:
你也可以将一个元素抽出然后插入到文档的其他地方:
from BeautifulSoup import BeautifulSoup text = "<html>There's <b>no</b> business like <b>show</b> business</html>" soup = BeautifulSoup(text) no, show = soup.findAll('b') show.replaceWith(no) print soup # <html>There's business like <b>no</b> business</html>
添加一个全新的元素
The Tag
class and the parser classes support a method called insert
. It works just like a Python list's insert
method: it takes an index to the tag's contents
member, and sticks a new element in that slot.
标签类和剖析类有一个insert方法,它就像Python列表的insert
方法: 它使用索引来定位标签的contents
成员,然后在那个位置插入一个新的元素。
This was demonstrated in the previous section, when we replaced a tag in the document with a brand new tag. You can use insert
to build up an entire parse tree from scratch:
在前面那个小结中,我们在文档替换一个新的标签时有用到这个方法。你可以使用insert
来重新构建整个剖析树:
from BeautifulSoup import BeautifulSoup, Tag, NavigableString soup = BeautifulSoup() tag1 = Tag(soup, "mytag") tag2 = Tag(soup, "myOtherTag") tag3 = Tag(soup, "myThirdTag") soup.insert(0, tag1) tag1.insert(0, tag2) tag1.insert(1, tag3) print soup # <mytag><myOtherTag></myOtherTag><myThirdTag></myThirdTag></mytag> text = NavigableString("Hello!") tag3.insert(0, text) print soup # <mytag><myOtherTag></myOtherTag><myThirdTag>Hello!</myThirdTag></mytag>
An element can occur in only one place in one parse tree. If you give insert
an element that's already connected to a soup object, it gets disconnected (with extract
) before it gets connected elsewhere. In this example, I try to insert my NavigableString
into a second part of the soup, but it doesn't get inserted again. It gets moved:
一个元素可能只在剖析树中出现一次。如果你给insert
的元素已经和soup对象所关联, 它会被取消关联(使用extract
)在它在被连接别的地方之前。在这个例子中,我试着插入我的NavigableString
到 soup对象的第二部分,但是它并没有被再次插入而是被移动了:
tag2.insert(0, text) print soup # <mytag><myOtherTag>Hello!</myOtherTag><myThirdTag></myThirdTag></mytag>
This happens even if the element previously belonged to a completely different soup object. An element can only have one parent
, one nextSibling
, et cetera, so it can only be in one place at a time.
即使这个元素属于一个完全不同的soup对象,还是会这样。 一个元素只可以有一个parent
,一个nextSibling
等等,也就是说一个地方只能出现一次。
常见问题(Troubleshooting)
This section covers common problems people have with Beautiful Soup. 这一节是使用BeautifulSoup时会遇到的一些常见问题的解决方法。
为什么Beautiful Soup不能打印我的no-ASCII字符?
If you're getting errors that say: "'ascii' codec can't encode character 'x' in position y: ordinal not in range(128)"
, the problem is probably with your Python installation rather than with Beautiful Soup. Try printing out the non-ASCII characters without running them through Beautiful Soup and you should have the same problem. For instance, try running code like this:
如果你遇到这样的错误: "'ascii' codec can't encode character 'x' in position y: ordinal not in range(128)"
, 这个错误可能是Python的问题而不是BeautifulSoup。
(译者注:在已知文档编码类型的情况下,可以先将编码转换为unicode形式,在转换为utf-8编码,然后才传递给BeautifulSoup。 例如HTML的内容htm是GB2312编码:
htm=unicode(htm,'gb2312','ignore').encode('utf-8','ignore')
soup=BeautifulSoup(htm)
如果不知道编码的类型,可以使用chardet先检测一下文档的编码类型。chardet需要自己安装一下,在网上很容下到。)
试着不用Beautiful Soup而直接打印non-ASCII 字符,你也会遇到一样的问题。 例如,试着运行以下代码:
latin1word = 'Sacr\xe9 bleu!' unicodeword = unicode(latin1word, 'latin-1') print unicodeword
If this works but Beautiful Soup doesn't, there's probably a bug in Beautiful Soup. However, if this doesn't work, the problem's with your Python setup. Python is playing it safe and not sending non-ASCII characters to your terminal. There are two ways to override this behavior.
如果它没有问题而Beautiful Soup不行,这可能是BeautifulSoup的一个bug。 但是,如果这个也有问题,就是Python本身的问题。Python为了安全缘故不支持发送non-ASCII 到终端。有两种方法可以解决这个限制。
The easy way is to remap standard output to a converter that's not afraid to send ISO-Latin-1 or UTF-8 characters to the terminal.
最简单的方式是将标准输出重新映射到一个转换器,不在意发送到终端的字符类型是ISO-Latin-1还是UTF-8字符串。import codecs import sys streamWriter = codecs.lookup('utf-8')[-1] sys.stdout = streamWriter(sys.stdout)
codecs.lookup
returns a number of bound methods and other objects related to a codec. The last one is aStreamWriter
object capable of wrapping an output stream.codecs.lookup
返回一些绑定的方法和其它和codec相关的对象。 最后一行是一个封装了输出流的StreamWriter
对象。The hard way is to create a
sitecustomize.py
file in your Python installation which sets the default encoding to ISO-Latin-1 or to UTF-8. Then all your Python programs will use that encoding for standard output, without you having to do something for each program. In my installation, I have a/usr/lib/python/sitecustomize.py
which looks like this:
稍微困难点的方法是创建一个sitecustomize.py
文件在你的Python安装中, 将默认编码设置为ISO-Latin-1或UTF-8。这样你所有的Python程序都会使用这个编码作为标准输出, 不用在每个程序里再设置一下。在我的安装中,我有一个/usr/lib/python/sitecustomize.py
,内容如下:import sys sys.setdefaultencoding("utf-8")
For more information about Python's Unicode support, look at Unicode for Programmers or End to End Unicode Web Applications in Python. Recipes 1.20 and 1.21 in the Python cookbook are also very helpful.
更多关于Python的Unicode支持的信息,参考 Unicode for Programmers or End to End Unicode Web Applications in Python。Python食谱的给的菜谱1.20和1.21也很有用。
Remember, even if your terminal display is restricted to ASCII, you can still use Beautiful Soup to parse, process, and write documents in UTF-8 and other encodings. You just can't print certain strings with print
.
但是即使你的终端显示被限制为ASCII,你也可以使用BeautifulSoup以UTF-8和其它的编码类型来剖析,处理和修改文档。 只是对于某些字符,你不能使用print
来输出。
Beautiful Soup 弄丢了我给的数据!为什么?为什么?????
Beautiful Soup can handle poorly-structured SGML, but sometimes it loses data when it gets stuff that's not SGML at all. This is not nearly as common as poorly-structured markup, but if you're building a web crawler or something you'll surely run into it.
Beautiful Soup可以处理结构不太规范的SGML,但是给它的材料非常不规范, 它会丢失数据。如果你是在写一个网络爬虫之类的程序,你肯定会遇到这种,不太常见的结构有问题的文档。
The only solution is to sanitize the data ahead of time with a regular expression. Here are some examples that I and Beautiful Soup users have discovered:
唯一的解决方法是先使用正则表达式来规范数据。 下面是一些我和一些Beautiful Soup的使用者发现的例子:
Beautiful Soup treats ill-formed XML definitions as data. However, it loses well-formed XML definitions that don't actually exist:
Beautiful Soup 将不规范德XML定义处理为数据(data)。然而,它丢失了那些实际上不存在的良好的XML定义:from BeautifulSoup import BeautifulSoup BeautifulSoup("< ! FOO @=>") # < ! FOO @=> BeautifulSoup("<b><!FOO>!</b>") # <b>!</b>
If your document starts a declaration and never finishes it, Beautiful Soup assumes the rest of your document is part of the declaration. If the document ends in the middle of the declaration, Beautiful Soup ignores the declaration totally. A couple examples:
如果你的文档开始了声明但却没有关闭,Beautiful Soup假定你的文档的剩余部分都是这个声明的一部分。 如果文档在声明的中间结束了,Beautiful Soup会忽略这个声明。如下面这个例子:from BeautifulSoup import BeautifulSoup BeautifulSoup("foo<!bar") # foo soup = BeautifulSoup("<html>foo<!bar</html>") print soup.prettify() # <html> # foo<!bar</html> # </html>
There are a couple ways to fix this; one is detailed here.
有几种方法来处理这种情况;其中一种在 这里有详细介绍。Beautiful Soup also ignores an entity reference that's not finished by the end of the document:
Beautiful Soup 也会忽略实体引用,如果它没有在文档结束的时候关闭:BeautifulSoup("<foo>") # <foo
I've never seen this in real web pages, but it's probably out there somewhere. 我从来没有在实际的网页中遇到这种情况,但是也许别的地方会出现。
A malformed comment will make Beautiful Soup ignore the rest of the document. This is covered as the example in Sanitizing Bad Data with Regexps.
一个畸形的注释会是Beautiful Soup回来文档的剩余部分。在使用正则规范数据这里有详细的例子。
The parse tree built by the BeautifulSoup
class offends my senses!
BeautifulSoup
类构建的剖析树让我感到头痛。
To get your markup parsed differently, check out
尝试一下别的剖析方法,试试 其他内置的剖析器,或者 自定义一个剖析器.
Beautiful Soup 太慢了!
Beautiful Soup will never run as fast as ElementTree or a custom-built SGMLParser
subclass. ElementTree is written in C, and SGMLParser
lets you write your own mini-Beautiful Soup that only does what you want. The point of Beautiful Soup is to save programmer time, not processor time.
Beautiful Soup 不会像ElementTree或者自定义的SGMLParser
子类一样快。 ElementTree是用C写的,并且做那些你想要做的事。 Beautiful Soup是用来节省程序员的时间,而不是处理器的时间。
That said, you can speed up Beautiful Soup quite a lot by only parsing the parts of the document you need, and you can make unneeded objects get garbage-collected by usingextract
.
但是你可以加快Beautiful Soup通过解析部分的文档,
高级主题
That does it for the basic usage of Beautiful Soup. But HTML and XML are tricky, and in the real world they're even trickier. So Beautiful Soup keeps some extra tricks of its own up its sleeve.
那些是对Beautiful Soup的基本用法。但是现实中的HTML和XML是非常棘手的(tricky),即使他们不是trickier。 因此Beautiful Soup也有一些额外的技巧。
产生器
The search methods described above are driven by generator methods. You can use these methods yourself: they're called nextGenerator
, previousGenerator
, nextSiblingGenerator
,previousSiblingGenerator
, and parentGenerator
. Tag
and parser objects also have childGenerator
and recursiveChildGenerator
available.
以上的搜索方法都是由产生器驱动的。你也可以自己使用这些方法: 他们是nextGenerator
, previousGenerator
, nextSiblingGenerator
, previousSiblingGenerator
, 和parentGenerator
. Tag
和剖析对象 可以使用childGenerator
和recursiveChildGenerator
。
Here's a simple example that strips HTML tags out of a document by iterating over the document and collecting all the strings.
下面是一个简单的例子,将遍历HTML的标签并将它们从文档中剥离,搜集所有的字符串:
from BeautifulSoup import BeautifulSoup soup = BeautifulSoup("""<div>You <i>bet</i> <a href="http://www.crummy.com/software/BeautifulSoup/">BeautifulSoup</a> rocks!</div>""") ''.join([e for e in soup.recursiveChildGenerator() if isinstance(e,unicode)]) # u'You bet\nBeautifulSoup\nrocks!'
Here's a more complex example that uses recursiveChildGenerator
to iterate over the elements of a document, printing each one as it gets it. 这是一个稍微复杂点的使用recursiveChildGenerator
的例子来遍历文档中所有元素, 并打印它们。
from BeautifulSoup import BeautifulSoup soup = BeautifulSoup("1<a>2<b>3") g = soup.recursiveChildGenerator() while True: try: print g.next() except StopIteration: break # 1 # <a>2<b>3</b></a> # 2 # <b>3</b> # 3
- beautifulsoap 中文文档
- BeautifulSoap 安装和使用
- python beautifulsoap的安装
- beautifulsoap 安装失败问题
- 爬虫实例抓取并download with Beautifulsoap
- python 爬虫好文 urllib cookie beautifulsoap
- 从零开始学网络爬虫之BeautifulSoap
- Log4j的中文文档
- Log4j的中文文档
- PHP中文文档
- iptable中文学习文档
- iptable中文学习文档
- pear中文文档
- Makefile中文说明文档
- toad 中文文档
- AspEmail 5.0 中文文档
- Vim 中文文档
- W3C中文文档列表
- 内核启动参数
- Source Insight建立Linux内核代码工程的方法
- C语言 预处理命令
- 常用算法-(5)快速排序
- Android学习第一季Part07【31-35】
- beautifulsoap 中文文档
- 第十周实验报告(任务3)
- 学习资料
- vi查找命令
- html一些基础知识
- .htaccess实现301重定向
- OSB实战开发【三】部署 部署WebServices及其客户端应
- tiny6410基于SDBOOT通过NFS启动根文件系统
- 结构体对齐那点事