Merge ~phoenixsite/beautifulsoup:master into beautifulsoup:master

Proposed by Carlos Romero
Status: Merged
Merged at revision: c9fe6065804af362c1c65ea85fece3cac31c2e82
Proposed branch: ~phoenixsite/beautifulsoup:master
Merge into: beautifulsoup:master
Diff against target: 4117 lines (+4095/-0)
3 files modified
doc.es/Makefile (+130/-0)
doc.es/source/conf.py (+256/-0)
doc.es/source/index.rst (+3709/-0)
Reviewer Review Type Date Requested Status
Leonard Richardson Approve
Review via email: mp+458246@code.launchpad.net

Commit message

Added Spanish documentation

To post a comment you must log in.
Revision history for this message
Chris Papademetrious (chrispitude) wrote :

Hi Carlos - I have a pull request for documentation suggestions here:

https://code.launchpad.net/~chrispitude/beautifulsoup/+git/beautifulsoup/+merge/456268

When future updates are made to the English documentation, will you be making corresponding updates to the Spanish documentation?

Revision history for this message
Leonard Richardson (leonardr) wrote :

Carlos, can you translate the following sentence for me so that I can link to the Spanish documentation from the other languages?

"This document is also available in Spanish."

Revision history for this message
Leonard Richardson (leonardr) wrote :

I've put up the documentation here:

https://www.crummy.com/software/BeautifulSoup/bs4/doc.es/

I used machine translation to come up with "Este documento también está disponible en una traducción al español." for the link text; let me know if something else is better.

Thanks again for your hard work on this!

review: Approve
Revision history for this message
Carlos Romero (phoenixsite) wrote :

Hello, Chris. Yes, whenever the English documentation is updated in the master branch I will try to translate it to Spanish. If you need the translation on another branch before releasing it to master, just let me know in the comment section of the proposal or just create a post on the discussion group. I will try to look it up. Thank you.

Revision history for this message
Carlos Romero (phoenixsite) wrote :

Hello Leonard, that translation is good. Maybe it would be better "Este documento también está disponible en español.", but the difference is not really that important. Maybe better for future updates.

Thanks to you and all the contributors to this great package. Keep it up!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/doc.es/Makefile b/doc.es/Makefile
2new file mode 100644
3index 0000000..8c833d2
4--- /dev/null
5+++ b/doc.es/Makefile
6@@ -0,0 +1,130 @@
7+# Makefile for Sphinx documentation
8+#
9+
10+# You can set these variables from the command line.
11+SPHINXOPTS =
12+SPHINXBUILD = sphinx-build
13+PAPER =
14+BUILDDIR = build
15+
16+# Internal variables.
17+PAPEROPT_a4 = -D latex_paper_size=a4
18+PAPEROPT_letter = -D latex_paper_size=letter
19+ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
20+
21+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
22+
23+help:
24+ @echo "Please use \`make <target>' where <target> is one of"
25+ @echo " html to make standalone HTML files"
26+ @echo " dirhtml to make HTML files named index.html in directories"
27+ @echo " singlehtml to make a single large HTML file"
28+ @echo " pickle to make pickle files"
29+ @echo " json to make JSON files"
30+ @echo " htmlhelp to make HTML files and a HTML help project"
31+ @echo " qthelp to make HTML files and a qthelp project"
32+ @echo " devhelp to make HTML files and a Devhelp project"
33+ @echo " epub to make an epub"
34+ @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
35+ @echo " latexpdf to make LaTeX files and run them through pdflatex"
36+ @echo " text to make text files"
37+ @echo " man to make manual pages"
38+ @echo " changes to make an overview of all changed/added/deprecated items"
39+ @echo " linkcheck to check all external links for integrity"
40+ @echo " doctest to run all doctests embedded in the documentation (if enabled)"
41+
42+clean:
43+ -rm -rf $(BUILDDIR)/*
44+
45+html:
46+ $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
47+ @echo
48+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
49+
50+dirhtml:
51+ $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
52+ @echo
53+ @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
54+
55+singlehtml:
56+ $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
57+ @echo
58+ @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
59+
60+pickle:
61+ $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
62+ @echo
63+ @echo "Build finished; now you can process the pickle files."
64+
65+json:
66+ $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
67+ @echo
68+ @echo "Build finished; now you can process the JSON files."
69+
70+htmlhelp:
71+ $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
72+ @echo
73+ @echo "Build finished; now you can run HTML Help Workshop with the" \
74+ ".hhp project file in $(BUILDDIR)/htmlhelp."
75+
76+qthelp:
77+ $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
78+ @echo
79+ @echo "Build finished; now you can run "qcollectiongenerator" with the" \
80+ ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
81+ @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/BeautifulSoup.qhcp"
82+ @echo "To view the help file:"
83+ @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/BeautifulSoup.qhc"
84+
85+devhelp:
86+ $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
87+ @echo
88+ @echo "Build finished."
89+ @echo "To view the help file:"
90+ @echo "# mkdir -p $$HOME/.local/share/devhelp/BeautifulSoup"
91+ @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/BeautifulSoup"
92+ @echo "# devhelp"
93+
94+epub:
95+ $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
96+ @echo
97+ @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
98+
99+latex:
100+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
101+ @echo
102+ @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
103+ @echo "Run \`make' in that directory to run these through (pdf)latex" \
104+ "(use \`make latexpdf' here to do that automatically)."
105+
106+latexpdf:
107+ $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
108+ @echo "Running LaTeX files through pdflatex..."
109+ make -C $(BUILDDIR)/latex all-pdf
110+ @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
111+
112+text:
113+ $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
114+ @echo
115+ @echo "Build finished. The text files are in $(BUILDDIR)/text."
116+
117+man:
118+ $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
119+ @echo
120+ @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
121+
122+changes:
123+ $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
124+ @echo
125+ @echo "The overview file is in $(BUILDDIR)/changes."
126+
127+linkcheck:
128+ $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
129+ @echo
130+ @echo "Link check complete; look for any errors in the above output " \
131+ "or in $(BUILDDIR)/linkcheck/output.txt."
132+
133+doctest:
134+ $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
135+ @echo "Testing of doctests in the sources finished, look at the " \
136+ "results in $(BUILDDIR)/doctest/output.txt."
137diff --git a/doc.es/source/6.1.jpg b/doc.es/source/6.1.jpg
138new file mode 100644
139index 0000000..97014f0
140Binary files /dev/null and b/doc.es/source/6.1.jpg differ
141diff --git a/doc.es/source/conf.py b/doc.es/source/conf.py
142new file mode 100644
143index 0000000..42fcf6d
144--- /dev/null
145+++ b/doc.es/source/conf.py
146@@ -0,0 +1,256 @@
147+# -*- coding: utf-8 -*-
148+#
149+# Beautiful Soup documentation build configuration file, created by
150+# sphinx-quickstart on Thu Jan 26 11:22:55 2012.
151+#
152+# This file is execfile()d with the current directory set to its containing dir.
153+#
154+# Note that not all possible configuration values are present in this
155+# autogenerated file.
156+#
157+# All configuration values have a default; values that are commented out
158+# serve to show the default.
159+
160+import sys, os
161+
162+# If extensions (or modules to document with autodoc) are in another directory,
163+# add these directories to sys.path here. If the directory is relative to the
164+# documentation root, use os.path.abspath to make it absolute, like shown here.
165+#sys.path.insert(0, os.path.abspath('.'))
166+
167+# -- General configuration -----------------------------------------------------
168+
169+# If your documentation needs a minimal Sphinx version, state it here.
170+#needs_sphinx = '1.0'
171+
172+# Add any Sphinx extension module names here, as strings. They can be extensions
173+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
174+extensions = []
175+
176+# Add any paths that contain templates here, relative to this directory.
177+templates_path = ['_templates']
178+
179+# The suffix of source filenames.
180+source_suffix = '.rst'
181+
182+# The encoding of source files.
183+#source_encoding = 'utf-8-sig'
184+
185+# The master toctree document.
186+master_doc = 'index'
187+
188+# General information about the project.
189+project = u'Beautiful Soup'
190+copyright = u'2004-2024, Leonard Richardson'
191+
192+# The version info for the project you're documenting, acts as replacement for
193+# |version| and |release|, also used in various other places throughout the
194+# built documents.
195+#
196+# The short X.Y version.
197+version = '4'
198+# The full version, including alpha/beta/rc tags.
199+release = '4.12.0'
200+
201+# The language for content autogenerated by Sphinx. Refer to documentation
202+# for a list of supported languages.
203+language = "es"
204+
205+# There are two options for replacing |today|: either, you set today to some
206+# non-false value, then it is used:
207+#today = ''
208+# Else, today_fmt is used as the format for a strftime call.
209+#today_fmt = '%B %d, %Y'
210+
211+# List of patterns, relative to source directory, that match files and
212+# directories to ignore when looking for source files.
213+exclude_patterns = []
214+
215+# The reST default role (used for this markup: `text`) to use for all documents.
216+#default_role = None
217+
218+# If true, '()' will be appended to :func: etc. cross-reference text.
219+#add_function_parentheses = True
220+
221+# If true, the current module name will be prepended to all description
222+# unit titles (such as .. function::).
223+#add_module_names = True
224+
225+# If true, sectionauthor and moduleauthor directives will be shown in the
226+# output. They are ignored by default.
227+#show_authors = False
228+
229+# The name of the Pygments (syntax highlighting) style to use.
230+pygments_style = 'sphinx'
231+
232+# A list of ignored prefixes for module index sorting.
233+#modindex_common_prefix = []
234+
235+
236+# -- Options for HTML output ---------------------------------------------------
237+
238+# The theme to use for HTML and HTML Help pages. See the documentation for
239+# a list of builtin themes.
240+html_theme = 'default'
241+
242+# Theme options are theme-specific and customize the look and feel of a theme
243+# further. For a list of options available for each theme, see the
244+# documentation.
245+#html_theme_options = {}
246+
247+# Add any paths that contain custom themes here, relative to this directory.
248+#html_theme_path = []
249+
250+# The name for this set of Sphinx documents. If None, it defaults to
251+# "<project> v<release> documentation".
252+#html_title = None
253+
254+# A shorter title for the navigation bar. Default is the same as html_title.
255+#html_short_title = None
256+
257+# The name of an image file (relative to this directory) to place at the top
258+# of the sidebar.
259+#html_logo = None
260+
261+# The name of an image file (within the static path) to use as favicon of the
262+# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
263+# pixels large.
264+#html_favicon = None
265+
266+# Add any paths that contain custom static files (such as style sheets) here,
267+# relative to this directory. They are copied after the builtin static files,
268+# so a file named "default.css" will overwrite the builtin "default.css".
269+html_static_path = ['_static']
270+
271+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
272+# using the given strftime format.
273+#html_last_updated_fmt = '%b %d, %Y'
274+
275+# If true, SmartyPants will be used to convert quotes and dashes to
276+# typographically correct entities.
277+#html_use_smartypants = True
278+
279+# Custom sidebar templates, maps document names to template names.
280+#html_sidebars = {}
281+
282+# Additional templates that should be rendered to pages, maps page names to
283+# template names.
284+#html_additional_pages = {}
285+
286+# If false, no module index is generated.
287+#html_domain_indices = True
288+
289+# If false, no index is generated.
290+#html_use_index = True
291+
292+# If true, the index is split into individual pages for each letter.
293+#html_split_index = False
294+
295+# If true, links to the reST sources are added to the pages.
296+#html_show_sourcelink = True
297+
298+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
299+#html_show_sphinx = True
300+
301+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
302+#html_show_copyright = True
303+
304+# If true, an OpenSearch description file will be output, and all pages will
305+# contain a <link> tag referring to it. The value of this option must be the
306+# base URL from which the finished HTML is served.
307+#html_use_opensearch = ''
308+
309+# This is the file name suffix for HTML files (e.g. ".xhtml").
310+#html_file_suffix = None
311+
312+# Output file base name for HTML help builder.
313+htmlhelp_basename = 'BeautifulSoupdoc'
314+
315+
316+# -- Options for LaTeX output --------------------------------------------------
317+
318+# The paper size ('letter' or 'a4').
319+#latex_paper_size = 'letter'
320+
321+# The font size ('10pt', '11pt' or '12pt').
322+#latex_font_size = '10pt'
323+
324+# Grouping the document tree into LaTeX files. List of tuples
325+# (source start file, target name, title, author, documentclass [howto/manual]).
326+latex_documents = [
327+ ('index', 'BeautifulSoup.tex', u'Beautiful Soup Documentation',
328+ u'Leonard Richardson', 'manual'),
329+]
330+
331+# The name of an image file (relative to this directory) to place at the top of
332+# the title page.
333+#latex_logo = None
334+
335+# For "manual" documents, if this is true, then toplevel headings are parts,
336+# not chapters.
337+#latex_use_parts = False
338+
339+# If true, show page references after internal links.
340+#latex_show_pagerefs = False
341+
342+# If true, show URL addresses after external links.
343+#latex_show_urls = False
344+
345+# Additional stuff for the LaTeX preamble.
346+#latex_preamble = ''
347+
348+# Documents to append as an appendix to all manuals.
349+#latex_appendices = []
350+
351+# If false, no module index is generated.
352+#latex_domain_indices = True
353+
354+
355+# -- Options for manual page output --------------------------------------------
356+
357+# One entry per manual page. List of tuples
358+# (source start file, name, description, authors, manual section).
359+man_pages = [
360+ ('index', 'beautifulsoup', u'Beautiful Soup Documentation',
361+ [u'Leonard Richardson'], 1)
362+]
363+
364+
365+# -- Options for Epub output ---------------------------------------------------
366+
367+# Bibliographic Dublin Core info.
368+epub_title = u'Beautiful Soup'
369+epub_author = u'Leonard Richardson'
370+epub_publisher = u'Leonard Richardson'
371+epub_copyright = u'2012, Leonard Richardson'
372+
373+# The language of the text. It defaults to the language option
374+# or en if the language is not set.
375+#epub_language = ''
376+
377+# The scheme of the identifier. Typical schemes are ISBN or URL.
378+#epub_scheme = ''
379+
380+# The unique identifier of the text. This can be a ISBN number
381+# or the project homepage.
382+#epub_identifier = ''
383+
384+# A unique identification for the text.
385+#epub_uid = ''
386+
387+# HTML files that should be inserted before the pages created by sphinx.
388+# The format is a list of tuples containing the path and title.
389+#epub_pre_files = []
390+
391+# HTML files shat should be inserted after the pages created by sphinx.
392+# The format is a list of tuples containing the path and title.
393+#epub_post_files = []
394+
395+# A list of files that should not be packed into the epub file.
396+#epub_exclude_files = []
397+
398+# The depth of the table of contents in toc.ncx.
399+#epub_tocdepth = 3
400+
401+# Allow duplicate toc entries.
402+#epub_tocdup = True
403diff --git a/doc.es/source/index.rst b/doc.es/source/index.rst
404new file mode 100644
405index 0000000..53c47f5
406--- /dev/null
407+++ b/doc.es/source/index.rst
408@@ -0,0 +1,3709 @@
409+.. _manual:
410+
411+=================================
412+ Documentación de Beautiful Soup
413+=================================
414+
415+.. py:module:: bs4
416+
417+.. image:: 6.1.jpg
418+ :align: right
419+ :alt: "El lacayo-pez empezó por sacarse de debajo del brazo una gran carta,
420+ casi tan grande como él."
421+
422+`Beautiful Soup <http://www.crummy.com/software/BeautifulSoup/>`_ es una
423+librería de Python para extraer datos de archivos en formato HTML y XML.
424+Trabaja con tu analizador favorito para ofrecer maneras bien definidas
425+de navegar, buscar y modificar el árbol analizado. Puede llegar a ahorrar
426+horas o días de trabajo a los programadores.
427+
428+Este manual ilustra con ejemplos la funcionalidades más importantes
429+de Beautiful Soup 4. Te muestro las cosas para las que la librería es buena,
430+cómo funciona, cómo usarla, cómo hacer lo que quieres y qué hacer cuando
431+no se cumplen tus expectativas.
432+
433+Este documento cubre Beautiful Soup versión 4.12.1. Los ejemplos en este
434+documento fueron escritos para Python 3.8.
435+
436+Podrías estar buscando la documentación de `Beautiful Soup 3
437+<http://www.crummy.com/software/BeautifulSoup/bs3/documentation.html>`_.
438+Si es así, debes saber que Beautiful Soup 3 ya no se desarrolla y
439+su soporte fue abandonado el 31 de diciembre de 2020. Si quieres
440+conocer la diferencias entre Beautiful Soup 3 y Beautiful Soup 4,
441+mira `Actualizar el código a BS4`_.
442+
443+Esta documentación ha sido traducida a otras lenguas por los usuarios
444+de Beautiful Soup:
445+
446+* `这篇文档当然还有中文版. <https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/>`_
447+* このページは日本語で利用できます(`外部リンク <http://kondou.com/BS4/>`_)
448+* `이 문서는 한국어 번역도 가능합니다. <https://www.crummy.com/software/BeautifulSoup/bs4/doc.ko/>`_
449+* `Este documento também está disponível em Português do Brasil. <https://www.crummy.com/software/BeautifulSoup/bs4/doc.ptbr>`_
450+* `Эта документация доступна на русском языке. <https://www.crummy.com/software/BeautifulSoup/bs4/doc.ru/>`_
451+
452+Cómo conseguir ayuda
453+====================
454+Si tienes alguna pregunta sobre BeautifulSoup, o si tienes problemas,
455+`envía un correo electrónico al grupo de discusión
456+<https://groups.google.com/forum/?fromgroups#!forunm/beautifulsoup>`_.
457+Si tienes algún problema relacionado con el análisis de un documento HTML,
458+asegúrate de mencionar :ref:`lo que la función diagnose() dice <diagnose>`
459+sobre dicho documento.
460+
461+Cuando informes de algún error en esta documentación, por favor,
462+indica la traducción que estás leyendo.
463+
464+===============
465+ Inicio rápido
466+===============
467+
468+Este es un documento HTML que usaré como ejemplo a lo largo de este
469+documento. Es parte de una historia de `Alicia en el país de las maravillas`::
470+
471+ html_doc = """<html><head><title>The Dormouse's story</title></head>
472+ <body>
473+ <p class="title"><b>The Dormouse's story</b></p>
474+
475+ <p class="story">Once upon a time there were three little sisters; and their names were
476+ <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
477+ <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
478+ <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
479+ and they lived at the bottom of a well.</p>
480+
481+ <p class="story">...</p>
482+ """
483+
484+Al procesar el documento de "Las tres hermanas" en Beautiful Soup, se nos
485+devuelve un objeto :py:class:`BeautifulSoup`, que representa el
486+documento como una estructura de datos anidada::
487+
488+ from bs4 import BeautifulSoup
489+ soup = BeautifulSoup(html_doc, 'html.parser')
490+
491+ print(soup.prettify())
492+ # <html>
493+ # <head>
494+ # <title>
495+ # The Dormouse's story
496+ # </title>
497+ # </head>
498+ # <body>
499+ # <p class="title">
500+ # <b>
501+ # The Dormouse's story
502+ # </b>
503+ # </p>
504+ # <p class="story">
505+ # Once upon a time there were three little sisters; and their names were
506+ # <a class="sister" href="http://example.com/elsie" id="link1">
507+ # Elsie
508+ # </a>
509+ # ,
510+ # <a class="sister" href="http://example.com/lacie" id="link2">
511+ # Lacie
512+ # </a>
513+ # and
514+ # <a class="sister" href="http://example.com/tillie" id="link3">
515+ # Tillie
516+ # </a>
517+ # ; and they lived at the bottom of a well.
518+ # </p>
519+ # <p class="story">
520+ # ...
521+ # </p>
522+ # </body>
523+ # </html>
524+
525+Estas son algunas de las maneras sencillas para navegar
526+por la estructura de datos::
527+
528+ soup.title
529+ # <title>The Dormouse's story</title>
530+
531+ soup.title.name
532+ # u'title'
533+
534+ soup.title.string
535+ # u'The Dormouse's story'
536+
537+ soup.title.parent.name
538+ # u'head'
539+
540+ soup.p
541+ # <p class="title"><b>The Dormouse's story</b></p>
542+
543+ soup.p['class']
544+ # u'title'
545+
546+ soup.a
547+ # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
548+
549+ soup.find_all('a')
550+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
551+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
552+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
553+
554+ soup.find(id="link3")
555+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
556+
557+Una tarea frecuente es extraer todas las URL encontradas en las etiquetas
558+<a> de una página::
559+
560+ for link in soup.find_all('a'):
561+ print(link.get('href'))
562+ # http://example.com/elsie
563+ # http://example.com/lacie
564+ # http://example.com/tillie
565+
566+Otra tarea habitual es extraer todo el texto de una página::
567+
568+ print(soup.get_text())
569+ # The Dormouse's story
570+ #
571+ # The Dormouse's story
572+ #
573+ # Once upon a time there were three little sisters; and their names were
574+ # Elsie,
575+ # Lacie and
576+ # Tillie;
577+ # and they lived at the bottom of a well.
578+ #
579+ # ...
580+
581+¿Esto se parece a lo que necesitas? Si es así, sigue leyendo.
582+
583+=========================
584+ Instalar Beautiful Soup
585+=========================
586+Si usas una versión reciente de Debian o Ubuntu Linux, puedes instalar
587+Beautiful Soup con el gestor de paquetes del sistema:
588+
589+:kbd:`$ apt-get install python3-bs4`
590+
591+Beautiful Soup 4 está publicado en Pypi, así que si no puedes instalarlo
592+con el gestor de paquetes, puedes instalarlo con ``easy_install`` o
593+``pip``. El nombre del paquete es ``beautifulsoup4``. Asegúrate de que
594+usas la versión correcta de ``pip`` o ``easy_install`` para tu versión
595+de Python (podrían llamarse ``pip3`` y ``easy_install3``, respectivamente):
596+
597+:kbd:`$ easy_install beautifulsoup4`
598+
599+:kbd:`$ pip install beautifulsoup4`
600+
601+(El paquete :py:class:`BeautifulSoup` ``no`` es el que quieres. Ese es
602+el lanzamiento anterior `Beautiful Soup 3`_. Muchos *software* utilizan
603+BS3, así que aún está disponible, pero si estás escribiendo nuevo código,
604+deberías instalar ``beautifulsoup4``).
605+
606+Si no tienes ``easy_install`` o ``pip`` instalados, puedes
607+`descargar el código de Beautiful Soup 4 comprimido en un tarball
608+<http://www.crummy.com/software/BeautifulSoup/download/4.x/>`_ e
609+instalarlo con ``setup.py``:
610+
611+:kbd:`$ python setup.py install`
612+
613+Si aún así todo falla, la licencia de Beautiful Soup te permite
614+empaquetar la librería completa con tu aplicación. Puedes descargar
615+el *tarball*, copiar su directorio ``bs4`` en tu base de código y
616+usar Beautiful Soup sin instalarlo en absoluto.
617+
618+Yo empleo Python 3.10 para desarrollar Beautiful Soup, aunque debería
619+funcionar con otras versiones recientes.
620+
621+.. _parser-installation:
622+
623+
624+Instalar un analizador
625+======================
626+
627+Beautiful Soup soporta el analizador de HTML incluido en la librería
628+estándar de Python, aunque también soporta varios analizadores de
629+Python de terceros. Uno de ellos es el `analizador de lxml <http://lxml.de/>`_.
630+Dependiendo de tu instalación, puedes instalar lxml con uno de los
631+siguientes comandos:
632+
633+:kbd:`$ apt-get install python-lxml`
634+
635+:kbd:`$ easy_install lxml`
636+
637+:kbd:`$ pip install lxml`
638+
639+Otra alternativa es usar el analizador de Python de
640+`html5lib <http://code.google.com/p/html5lib/>`_,
641+el cual analiza HTML de la misma manera en la que lo haría
642+un navegador web. Dependiendo de tu instalación, puedes instalar
643+html5lib con uno de los siguientes comandos:
644+
645+:kbd:`$ apt-get install python-html5lib`
646+
647+:kbd:`$ easy_install html5lib`
648+
649+:kbd:`$ pip install html5lib`
650+
651+Esta tabla resume las ventajas e inconvenientes de cada librería de los analizadores:
652+
653++-----------------------+--------------------------------------------+-----------------------------------+-----------------------------+
654+| Analizador | Uso típico | Ventajas | Desventajas |
655++-----------------------+--------------------------------------------+-----------------------------------+-----------------------------+
656+| html.parser de Python | ``BeautifulSoup(markup, "html.parser")`` | * Ya incluido | * No tan rápido como lxml, |
657+| | | * Rapidez decente | menos tolerante que |
658+| | | * Tolerante (en Python 3.2) | html5lib. |
659++-----------------------+--------------------------------------------+-----------------------------------+-----------------------------+
660+| Analizador HTML de | ``BeautifulSoup(markup, "lxml")`` | * Muy rápido | * Dependencia externa de C |
661+| lxml | | * Tolerante | |
662++-----------------------+--------------------------------------------+-----------------------------------+-----------------------------+
663+| Analizador XML de | ``BeautifulSoup(markup, "lxml-xml")`` | * Muy rápido | * Dependencia externa de C |
664+| lxml | ``BeautifulSoup(markup, "xml")`` | * El único analizador XML | |
665+| | | actualmente soportado | |
666++-----------------------+--------------------------------------------+-----------------------------------+-----------------------------+
667+| html5lib | ``BeautifulSoup(markup, "html5lib")`` | * Extremadamente tolerante | * Muy lento |
668+| | | * Analiza las páginas de la misma | * Dependencia externa de |
669+| | | manera que un navegador web | Python |
670+| | | * Crea HTML5 válido | |
671++-----------------------+--------------------------------------------+-----------------------------------+-----------------------------+
672+
673+Si puedes, te recomiendo que instales y uses lxml para mayor velocidad.
674+
675+Ten en cuenta que si un documento es inválido, analizadores diferentes
676+generarán árboles de Beautiful Soup diferentes para él. Mira
677+`Diferencias entre analizadores`_ para más detalle.
678+
679+==================
680+ Haciendo la sopa
681+==================
682+
683+Para analizar un documento pásalo al constructor de :py:class:`BeautifulSoup`.
684+Puedes pasar una cadena de caracteres o abrir un manejador de archivos::
685+
686+ from bs4 import BeautifulSoup
687+
688+ with open("index.html") as fp:
689+ soup = BeautifulSoup(fp, 'html.parser')
690+
691+ soup = BeautifulSoup("<html>a web page</html>", 'html.parser')
692+
693+Primero, el documento se convierte a Unicode, y las entidades HTML se
694+convierten a caracteres Unicode::
695+
696+ print(BeautifulSoup("<html><head></head><body>Sacr&eacute; bleu!</body></html>", "html.parser"))
697+ # <html><head></head><body>Sacré bleu!</body></html>
698+
699+Entonces Beautiful Soup analiza el documento usando el mejor analizador
700+disponible. Usará un analizador HTML a no ser que se especifique que se
701+use un analizador XML (ver `Analizar XML`_).
702+
703+==================
704+ Tipos de objetos
705+==================
706+
707+Beautiful Soup transforma un complejo documento HTML en un complejo árbol de objetos
708+de Python. Pero tan solo tendrás que lidiar con cuatro `tipos` de objetos: :py:class:`Tag`,
709+:py:class:`NavigableString`, :py:class:`BeautifulSoup` y :py:class:`Comment`.
710+
711+.. py:class:: Tag
712+
713+ Un objeto :py:class:`Tag` corresponde a una etiqueta XML o HTML en el documento
714+ original.
715+
716+ ::
717+
718+ soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'html.parser')
719+ tag = soup.b
720+ type(tag)
721+ # <class 'bs4.element.Tag'>
722+
723+ Las etiquetas tienen muchos atributos y métodos, y cubriré la mayoría de ellos en
724+ `Navegar por el árbol`_ y `Buscar en el árbol`_. Por ahora, las características
725+ más importantes de una etiqueta son su nombre y sus atributos.
726+
727+ .. py:attribute:: name
728+
729+ Toda etiqueta tiene un nombre::
730+
731+ tag.name
732+ # 'b'
733+
734+
735+ Si cambias el nombre de una etiqueta, el cambio se verá reflejado en
736+ cualquier especificación generada por Beautiful Soup a partir de entonces::
737+
738+ tag.name = "blockquote"
739+ tag
740+ # <blockquote class="boldest">Extremely bold</blockquote>
741+
742+ .. py:attribute:: attrs
743+
744+ Una etiqueta HTML o XML puede tener cualquier cantidad de atributos.
745+ La etiqueta ``<b id="boldest">`` tiene un atributo "id" cuyo valor
746+ es "boldest". Puedes acceder a los atributos de una etiqueta
747+ usándola como un diccionario::
748+
749+ tag = BeautifulSoup('<b id="boldest">bold</b>', 'html.parser').b
750+ tag['id']
751+ # 'boldest'
752+
753+ Puedes acceder a los atributos del diccionario directamente con ``.attrs``::
754+
755+ tag.attrs
756+ # {'id': 'boldest'}
757+
758+ Puedes añadir, quitar y modificar los atributos de una etiqueta. De nuevo, esto
759+ se realiza usando la etiqueta como un diccionario::
760+
761+ tag['id'] = 'verybold'
762+ tag['another-attribute'] = 1
763+ tag
764+ # <b another-attribute="1" id="verybold"></b>
765+
766+ del tag['id']
767+ del tag['another-attribute']
768+ tag
769+ # <b>bold</b>
770+
771+ tag['id']
772+ # KeyError: 'id'
773+ tag.get('id')
774+ # None
775+
776+ .. _multivalue:
777+
778+ Atributos multivaluados
779+ -----------------------
780+
781+ HTML 4 define algunos atributos que pueden tomar múltiples valores. HTML 5
782+ elimina un par de ellos, pero define unos cuantos más. El atributo multivaluado
783+ más común es ``class`` (esto es, una etiqueta puede tener más de una clase de CSS).
784+ Otros incluyen ``rel``, ``rev``, ``accept-charset``, ``headers`` y ``accesskey``.
785+ Por defecto, Beautiful Soup transforma los valores de un atributo multivaluado en
786+ una lista::
787+
788+ css_soup = BeautifulSoup('<p class="body"></p>', 'html.parser')
789+ css_soup.p['class']
790+ # ['body']
791+
792+ css_soup = BeautifulSoup('<p class="body strikeout"></p>', 'html.parser')
793+ css_soup.p['class']
794+ # ['body', 'strikeout']
795+
796+ Si un atributo `parece` que tiene más de un valor, pero no es un atributo
797+ multivaluado definido como tal por ninguna versión del estándar de HTML,
798+ Beautiful Soup no modificará el atributo::
799+
800+ id_soup = BeautifulSoup('<p id="my id"></p>', 'html.parser')
801+ id_soup.p['id']
802+ # 'my id'
803+
804+ Cuando transformas una etiqueta en una cadena de caracteres, muchos atributos
805+ se combinan::
806+
807+ rel_soup = BeautifulSoup('<p>Back to the <a rel="index first">homepage</a></p>', 'html.parser')
808+ rel_soup.a['rel']
809+ # ['index', 'first']
810+ rel_soup.a['rel'] = ['index', 'contents']
811+ print(rel_soup.p)
812+ # <p>Back to the <a rel="index contents">homepage</a></p>
813+
814+ Puedes forzar que todos los atributos sean analizados como cadenas
815+ de caracteres pasando ``multi_valued_attributes=None`` como argumento
816+ clave en el constructor de :py:class:`BeautifulSoup`::
817+
818+ no_list_soup = BeautifulSoup('<p class="body strikeout"></p>', 'html.parser', multi_valued_attributes=None)
819+ no_list_soup.p['class']
820+ # 'body strikeout'
821+
822+ Puedes usar ``get_attribute_list`` para obtener un valor que siempre sea una lista,
823+ sin importar si es un atributo multivaluado::
824+
825+ id_soup.p.get_attribute_list('id')
826+ # ["my id"]
827+
828+ Si analizas un documento como XML, no hay atributos multivaluados::
829+
830+ xml_soup = BeautifulSoup('<p class="body strikeout"></p>', 'xml')
831+ xml_soup.p['class']
832+ # 'body strikeout'
833+
834+ Una vez más, puedes configurar esto usando el argumento ``multi_valued_attributes`` ::
835+
836+ class_is_multi= { '*' : 'class'}
837+ xml_soup = BeautifulSoup('<p class="body strikeout"></p>', 'xml', multi_valued_attributes=class_is_multi)
838+ xml_soup.p['class']
839+ # ['body', 'strikeout']
840+
841+ Probablemente no tengas que hacer esto, pero si lo necesitas, usa los
842+ parámetros por defecto como guía. Implementan las reglas descritas en la
843+ especificación de HTML::
844+
845+ from bs4.builder import builder_registry
846+ builder_registry.lookup('html').DEFAULT_CDATA_LIST_ATTRIBUTES
847+
848+.. py:class:: NavigableString
849+
850+-----------------------------
851+
852+Un *string* corresponde a un trozo de texto en una etiqueta. Beautiful Soup usa la clase
853+:py:class:`NavigableString` para contener estos trozos de texto::
854+
855+ soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'html.parser')
856+ tag = soup.b
857+ tag.string
858+ # 'Extremely bold'
859+ type(tag.string)
860+ # <class 'bs4.element.NavigableString'>
861+
862+Un :py:class:`NavigableString` es como una cadena de caracteres de Python Unicode,
863+exceptuando que también soporta algunas de las características descritas en
864+`Navegar por el árbol`_ y `Buscar en el árbol`_. Puedes convertir un objeto
865+:py:class:`NavigableString` a una cadena de caracteres Unicode usando ``str``::
866+
867+ unicode_string = str(tag.string)
868+ unicode_string
869+ # 'Extremely bold'
870+ type(unicode_string)
871+ # <type 'str'>
872+
873+No puedes editar dicha cadena, pero puedes reemplazar una cadena por otra, usando
874+:ref:`replace_with()`::
875+
876+ tag.string.replace_with("No longer bold")
877+ tag
878+ # <b class="boldest">No longer bold</b>
879+
880+:py:class:`NavigableString` soporta la mayoría de las características descritas en
881+`Navegar por el árbol`_ y `Buscar en el árbol`_, pero no todas.
882+En particular, como una cadena no puede contener nada (la manera en la que
883+una etiqueta contiene una cadena de caracteres u otra etiqueta), *strings* no
884+admiten los atributos `.contents`` o ``.string``, o el método ``find()``.
885+
886+Si quieres usar un :py:class:`NavigableString` fuera de Beautiful Soup,
887+deberías llamar ``unicode()`` sobre él para convertirlo en una cadena de caracteres
888+de Python Unicode. Si no, tu cadena arrastrará una referencia a todo el árbol analizado
889+de Beautiful Soup, incluso cuando hayas acabado de utilizar Beautiful Soup. Esto es un
890+gran malgasto de memoria.
891+
892+.. py:class:: BeautifulSoup
893+
894+---------------------------
895+
896+El objeto :py:class:`BeautifulSoup` representa el documento analizado
897+en su conjunto. Para la mayoría de propósitos, puedes usarlo como un objeto
898+:py:class:`Tag`. Esto significa que soporta la mayoría de métodos descritos
899+en `Navegar por el árbol`_ and `Buscar en el árbol`_.
900+
901+Puedes también pasar un objeto :py:class:`BeautifulSoup` en cualquiera de
902+los métodos definidos en `Modificar el árbol`_, como si fuese un :py:class:`Tag`.
903+Esto te permite hacer cosas como combinar dos documentos analizados::
904+
905+ doc = BeautifulSoup("<document><content/>INSERT FOOTER HERE</document", "xml")
906+ footer = BeautifulSoup("<footer>Here's the footer</footer>", "xml")
907+ doc.find(text="INSERT FOOTER HERE").replace_with(footer)
908+ # 'INSERT FOOTER HERE'
909+ print(doc)
910+ # <?xml version="1.0" encoding="utf-8"?>
911+ # <document><content/><footer>Here's the footer</footer></document>
912+
913+Como un objeto :py:class:`BeautifulSoup` no corresponde realmente con una
914+etiqueta HTML o XML, no tiene nombre ni atributos. Aún así, es útil
915+comprobar su ``.name``, así que se le ha dado el ``.name`` especial
916+"[document]"::
917+
918+ soup.name
919+ # '[document]'
920+
921+Cadenas especiales
922+==================
923+
924+:py:class:`Tag`, :py:class:`NavigableString` y
925+:py:class:`BeautifulSoup` cubren la mayoría de todo lo que verás en
926+un archivo HTML o XML, aunque aún quedan algunos remanentes. El principal
927+que probablemente encuentres es el :py:class:`Comment`.
928+
929+.. py:class:: Comment
930+
931+::
932+
933+ markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
934+ soup = BeautifulSoup(markup, 'html.parser')
935+ comment = soup.b.string
936+ type(comment)
937+ # <class 'bs4.element.Comment'>
938+
939+El objeto :py:class:`Comment` es solo un tipo especial de :py:class:`NavigableString`::
940+
941+ comment
942+ # 'Hey, buddy. Want to buy a used parser'
943+
944+Pero cuando aparece como parte de un documento HTML, un :py:class:`Comment`
945+se muestra con un formato especial::
946+
947+ print(soup.b.prettify())
948+ # <b>
949+ # <!--Hey, buddy. Want to buy a used parser?-->
950+ # </b>
951+
952+Para documentos HTML
953+--------------------
954+
955+Beautiful Soup define algunas subclases de :py:class:`NavigableString`
956+para contener cadenas de caracteres encontradas dentro de etiquetas
957+HTML específicas. Esto hace más fácil tomar el cuerpo principal de la
958+página, ignorando cadenas que probablemente representen directivas de
959+programación encontradas dentro de la página. `(Estas clases son nuevas
960+en Beautiful Soup 4.9.0, y el analizador html5lib no las usa)`.
961+
962+.. py:class:: Stylesheet
963+
964+Una subclase de :py:class:`NavigableString` que representa hojas de estilo
965+CSS embebidas; esto es, cualquier cadena en una etiqueta
966+``<style>`` durante el análisis del documento.
967+
968+.. py:class:: Script
969+
970+Una subclase de :py:class:`NavigableString` que representa
971+JavaScript embebido; esto es, cualquier cadena en una etiqueta
972+``<script>`` durante el análisis del documento.
973+
974+.. py:class:: Template
975+
976+Una subclase de :py:class:NavigableString` que representa plantillas
977+HTML embebidas; esto es, cualquier cadena en una etiqueta ``<template>``
978+durante el análisis del documento.
979+
980+Para documentos XML
981+-------------------
982+
983+Beautiful Soup define algunas clases :py:class:`NavigableString`
984+para contener tipos especiales de cadenas de caracteres que pueden
985+ser encontradas en documentos XML. Como :py:class:`Comment`, estas
986+clases son subclases de :py:class:`NavigableString` que añaden
987+algo extra a la cadena de caracteres en la salida.
988+
989+.. py:class:: Declaration
990+
991+Una subclase de :py:class:`NavigableString` que representa la
992+`declaración <https://www.w3.org/TR/REC-xml/#sec-prolog-dtd>`_ al
993+principio de un documento XML.
994+
995+.. py:class:: Doctype
996+
997+Una subclase de :py:class:`NavigableString` que representa la
998+`declaración del tipo de documento <https://www.w3.org/TR/REC-xml/#dt-doctype>`_
999+que puede encontrarse cerca del comienzo de un documento XML.
1000+
1001+.. py:class:: CData
1002+
1003+Una subclase de :py:class:`NavigableString` que representa una
1004+`sección CData <https://www.w3.org/TR/REC-xml/#sec-cdata-sect>`_.
1005+
1006+.. py:class:: ProcessingInstruction
1007+
1008+Una subclase de :py:class:`NavigableString` que representa el contenido de
1009+una `instrucción de procesamiento XML <https://www.w3.org/TR/REC-xml/#sec-pi>`_.
1010+
1011+
1012+======================
1013+ Navegar por el árbol
1014+======================
1015+
1016+Aquí está el documento HTML de las "Tres hermanas" de nuevo::
1017+
1018+ html_doc = """
1019+ <html><head><title>The Dormouse's story</title></head>
1020+ <body>
1021+ <p class="title"><b>The Dormouse's story</b></p>
1022+
1023+ <p class="story">Once upon a time there were three little sisters; and their names were
1024+ <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
1025+ <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
1026+ <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
1027+ and they lived at the bottom of a well.</p>
1028+
1029+ <p class="story">...</p>
1030+ """
1031+
1032+ from bs4 import BeautifulSoup
1033+ soup = BeautifulSoup(html_doc, 'html.parser')
1034+
1035+Usaré este como ejemplo para enseñarte cómo mover una parte de un
1036+documento a otra.
1037+
1038+Bajar
1039+=====
1040+
1041+Las etiquetas pueden contener cadenas u otras etiquetas. Estos elementos
1042+son los hijos (`children`) de la etiqueta. Beautiful Soup ofrece muchos
1043+atributos para navegar e iterar por los hijos de una etiqueta.
1044+
1045+Debe notarse que las cadenas de Beautiful Soup no soportan ninguno
1046+de estos atributos, porque una cadena no puede tener hijos.
1047+
1048+Navegar usando nombres de etiquetas
1049+-----------------------------------
1050+
1051+La manera más simple de navegar por el árbol analizado es indicar
1052+el nombre de la etiqueta que quieres. Si quieres la etiqueta <head>,
1053+tan solo indica ``soup.head``::
1054+
1055+ soup.head
1056+ # <head><title>The Dormouse's story</title></head>
1057+
1058+ soup.title
1059+ # <title>The Dormouse's story</title>
1060+
1061+Puedes usar este truco una y otra vez para acercarte a una parte concreta
1062+del árbol analizado. Este código obtiene la primera etiqueta <b> dentro
1063+de la etiqueta <body>::
1064+
1065+ soup.body.b
1066+ # <b>The Dormouse's story</b>
1067+
1068+Usar el nombre de la etiqueta como atributo te dará solo la `primera`
1069+etiqueta con ese nombre::
1070+
1071+ soup.a
1072+ # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
1073+
1074+Si necesitas obtener `todas` las etiquetas <a>, o cualquier
1075+cosa más complicada que la primera etiqueta con cierto nombre, tendrás
1076+que usar uno de los métodos descritos en `Buscar en el árbol`_, como
1077+`find_all()`::
1078+
1079+ soup.find_all('a')
1080+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1081+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1082+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1083+
1084+``.contents`` y ``.children``
1085+-----------------------------
1086+
1087+Los hijos de una etiqueta están disponibles en una lista llamada
1088+``.contents``::
1089+
1090+ head_tag = soup.head
1091+ head_tag
1092+ # <head><title>The Dormouse's story</title></head>
1093+
1094+ head_tag.contents
1095+ # [<title>The Dormouse's story</title>]
1096+
1097+ title_tag = head_tag.contents[0]
1098+ title_tag
1099+ # <title>The Dormouse's story</title>
1100+ title_tag.contents
1101+ # ['The Dormouse's story']
1102+
1103+El objeto :py:class:`BeautifulSoup` por sí solo ya tiene hijos. En este caso,
1104+la etiqueta <html> is hija del objeto :py:class:`BeautifulSoup`.::
1105+
1106+ len(soup.contents)
1107+ # 1
1108+ soup.contents[0].name
1109+ # 'html'
1110+
1111+Una cadena no tiene ``.contents``, porque no puede contener nada::
1112+
1113+ text = title_tag.contents[0]
1114+ text.contents
1115+ # AttributeError: 'NavigableString' object has no attribute 'contents'
1116+
1117+En lugar de obtenerlos como una lista, puedes iterar sobre los hijos
1118+de una etiqueta usando el generador ``.children``::
1119+
1120+ for child in title_tag.children:
1121+ print(child)
1122+ # The Dormouse's story
1123+
1124+Si quieres modificar los hijos de una etiqueta, emplea los métodos
1125+descritos en `Modificar el árbol`_. No modifiques la lista
1126+``.contents`` directamente: eso podría ocasionar problemas que pueden
1127+ser sutiles y difíciles de detectar.
1128+
1129+
1130+``.descendants``
1131+----------------
1132+
1133+Los atributos ``.contents`` y ``.children`` tan solo consideran los
1134+hijos `directos` de una etiqueta. Por ejemplo, la etiqueta <head>
1135+tiene un único hijo directo--la etiqueta <title>::
1136+
1137+ head_tag.contents
1138+ # [<title>The Dormouse's story</title>]
1139+
1140+Pero la etiqueta <title> tiene un hijo: la cadena "The Dormouse's
1141+story". Puede dar la sensación de que esa cadena es también hija de
1142+la etiqueta <head>. El atributo ``.descendants`` te permite iterar
1143+sobre `todos` los hijos de una etiqueta recursivamente: sus hijos,
1144+hijos de sus hijos directos, y así sucesivamente::
1145+
1146+ for child in head_tag.descendants:
1147+ print(child)
1148+ # <title>The Dormouse's story</title>
1149+ # The Dormouse's story
1150+
1151+La etiqueta <head> tiene un solo hijo, pero tiene dos descendientes:
1152+la etiqueta <title> y el hijo de la etiqueta <title>. El objeto
1153+:py:class:`BeautifulSoup` tiene un hijo directo (la etiqueta <html>), pero
1154+tiene otros muchos descendientes::
1155+
1156+ len(list(soup.children))
1157+ # 1
1158+ len(list(soup.descendants))
1159+ # 26
1160+
1161+.. _.string:
1162+
1163+``.string``
1164+-----------
1165+
1166+Si una etiqueta tiene solo un hijo, y dicho hijo es un :py:class:`NavigableString`,
1167+el hijo se obtiene mediante ``.string``::
1168+
1169+ title_tag.string
1170+ # 'The Dormouse's story'
1171+
1172+Si el único hijo de una etiqueta es otra etiqueta, y `esa`
1173+etiqueta tiene un ``.string``, entonces se considera que
1174+la etiqueta madre tiene el mismo ``.string`` que su hijo::
1175+
1176+ head_tag.contents
1177+ # [<title>The Dormouse's story</title>]
1178+
1179+ head_tag.string
1180+ # 'The Dormouse's story'
1181+
1182+Si una etiqueta contiene más una cadena, entonces no está claro
1183+a qué se debería referir ``.string``, así que ``.string``
1184+pasa a valer ``None``::
1185+
1186+ print(soup.html.string)
1187+ # None
1188+
1189+.. _string-generators:
1190+
1191+``.strings`` y ``stripped_strings``
1192+-----------------------------------
1193+
1194+Si hay más de una cosa dentro de una etiqueta, puedes seguir
1195+obteniendo las cadenas. Usa el generador ``.string``::
1196+
1197+ for string in soup.strings:
1198+ print(repr(string))
1199+ '\n'
1200+ # "The Dormouse's story"
1201+ # '\n'
1202+ # '\n'
1203+ # "The Dormouse's story"
1204+ # '\n'
1205+ # 'Once upon a time there were three little sisters; and their names were\n'
1206+ # 'Elsie'
1207+ # ',\n'
1208+ # 'Lacie'
1209+ # ' and\n'
1210+ # 'Tillie'
1211+ # ';\nand they lived at the bottom of a well.'
1212+ # '\n'
1213+ # '...'
1214+ # '\n'
1215+
1216+Estas cadenas tienden a tener muchos espacios en blanco extra, los
1217+cuales puedes quitar usando el generador ``.stripped_strings``::
1218+
1219+ for string in soup.stripped_strings:
1220+ print(repr(string))
1221+ # "The Dormouse's story"
1222+ # "The Dormouse's story"
1223+ # 'Once upon a time there were three little sisters; and their names were'
1224+ # 'Elsie'
1225+ # ','
1226+ # 'Lacie'
1227+ # 'and'
1228+ # 'Tillie'
1229+ # ';\n and they lived at the bottom of a well.'
1230+ # '...'
1231+
1232+Aquí, las cadenas que consisten completamente en espacios en blanco
1233+se ignoran, y espacios en blanco al principio y final de las cadenas
1234+se eliminan.
1235+
1236+Subir
1237+=====
1238+
1239+Continuando con la analogía del árbol genealógico, toda etiqueta
1240+tiene una `madre`: la etiqueta que la contiene.
1241+
1242+.. _.parent:
1243+
1244+``.parent``
1245+-----------
1246+
1247+Puedes acceder a la madre de una etiqueta con el atributo ``.parent``. En
1248+el ejemplo de "Las tres hermanas", la etiqueta <head> es la madre
1249+de la etiqueta <title>::
1250+
1251+ title_tag = soup.title
1252+ title_tag
1253+ # <title>The Dormouse's story</title>
1254+ title_tag.parent
1255+ # <head><title>The Dormouse's story</title></head>
1256+
1257+El texto de título tiene una madre: la etiqueta <title> que lo
1258+contiene::
1259+
1260+ title_tag.string.parent
1261+ # <title>The Dormouse's story</title>
1262+
1263+La madre de una etiqueta de alto nivel como <html> es el objeto :py:class:`BeautifulSoup`
1264+mismo::
1265+
1266+ html_tag = soup.html
1267+ type(html_tag.parent)
1268+ # <class 'bs4.BeautifulSoup'>
1269+
1270+Y el ``.parent`` de un objeto :py:class:`BeautifulSoup` se define como ``None``::
1271+
1272+ print(soup.parent)
1273+ # None
1274+
1275+.. _.parents:
1276+
1277+``.parents``
1278+------------
1279+
1280+Puedes iterar sobre todas las madres de los elementos con
1281+``.parents``. Este ejemplo usa ``.parent`` para moverse' de una
1282+etiqueta <a> en medio del documento a lo más alto del documento::
1283+
1284+ link = soup.a
1285+ link
1286+ # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
1287+ for parent in link.parents:
1288+ print(parent.name)
1289+ # p
1290+ # body
1291+ # html
1292+ # [document]
1293+
1294+Hacia los lados
1295+===============
1296+
1297+Considera un documento sencillo como este::
1298+
1299+ sibling_soup = BeautifulSoup("<a><b>text1</b><c>text2</c></a>", 'html.parser')
1300+ print(sibling_soup.prettify())
1301+ # <a>
1302+ # <b>
1303+ # text1
1304+ # </b>
1305+ # <c>
1306+ # text2
1307+ # </c>
1308+ # </a>
1309+
1310+Las etiquetas <b> y <c> están al mismo nivel: son hijas directas de la misma
1311+etiqueta. Las llamamos `hermanas`. Cuando un documento está bien formateado,
1312+las hermanas están al mismo nivel de sangría. Puedes usar también esta
1313+relación en el código que escribas.
1314+
1315+``.next_sibling`` y ``.previous_sibling``
1316+-----------------------------------------
1317+
1318+Puedes usar ``.next_sibling`` y ``.previous_sibling`` para navegar
1319+entre elementos de la página que están al mismo nivel del árbol
1320+analizado::
1321+
1322+ sibling_soup.b.next_sibling
1323+ # <c>text2</c>
1324+
1325+ sibling_soup.c.previous_sibling
1326+ # <b>text1</b>
1327+
1328+La etiqueta <b> tiene un ``.next_sibling``, pero no ``.previous_sibling``,
1329+porque no hay nada antes de la etiqueta <b> `al mismo nivel del árbol`.
1330+Por la misma razón, la etiqueta <c> tiene un ``.previous_sibling`` pero no
1331+un ``.next_sibling``::
1332+
1333+ print(sibling_soup.b.previous_sibling)
1334+ # None
1335+ print(sibling_soup.c.next_sibling)
1336+ # None
1337+
1338+Las cadenas "text1" y "text2" `no` son hermanas, porque no tienen la misma
1339+madre::
1340+
1341+ sibling_soup.b.string
1342+ # 'text1'
1343+
1344+ print(sibling_soup.b.string.next_sibling)
1345+ # None
1346+
1347+En documentos reales, los ``.next_sibling`` o ``.previous_sibling`` de
1348+una etiqueta normalmente serán cadenas que contengan espacios en blanco.
1349+Retomando el documento de "Las tres hermanas"::
1350+
1351+ # <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
1352+ # <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
1353+ # <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
1354+
1355+Podrías pensar que la ``.next_sibling`` de la primera etiqueta <a> podría
1356+ser la segunda etiqueta <a>. Pero realmente es una cadena de caracteres:
1357+la coma y el salto de línea que separan la primera etiqueta <a> de la
1358+segunda::
1359+
1360+ link = soup.a
1361+ link
1362+ # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
1363+
1364+ link.next_sibling
1365+ # ',\n '
1366+
1367+La segunda etiqueta <a> es realmente la ``.next_sibling`` de la coma::
1368+
1369+ link.next_sibling.next_sibling
1370+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
1371+
1372+.. _sibling-generators:
1373+
1374+``.next_siblings`` y ``.previous_siblings``
1375+-------------------------------------------
1376+
1377+Puedes iterar sobre las hermanas de una etiqueta con ``.next_siblings`` o
1378+``.previuos_siblings``::
1379+
1380+ for sibling in soup.a.next_siblings:
1381+ print(repr(sibling))
1382+ # ',\n'
1383+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
1384+ # ' and\n'
1385+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
1386+ # '; and they lived at the bottom of a well.'
1387+
1388+ for sibling in soup.find(id="link3").previous_siblings:
1389+ print(repr(sibling))
1390+ # ' and\n'
1391+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
1392+ # ',\n'
1393+ # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
1394+ # 'Once upon a time there were three little sisters; and their names were\n'
1395+
1396+Hacia delante y hacia atrás
1397+===========================
1398+
1399+Échale un vistazo al comienzo del documento de "Las tres hermanas"::
1400+
1401+ # <html><head><title>The Dormouse's story</title></head>
1402+ # <p class="title"><b>The Dormouse's story</b></p>
1403+
1404+Un analizador HTML toma esta cadena de caracteres y la convierte en
1405+una serie de eventos: "se abre una etiqueta <html>", "se abre una
1406+etiqueta <head>", "se abre una etiqueta <title>", "se añade una cadena",
1407+"se cierra la etiqueta <title>", "se abre una etiqueta <p>" y así
1408+sucesivamente. Beautiful Soup ofrece herramientas para reconstruir
1409+el análisis inicial del documento.
1410+
1411+.. _element-generators:
1412+
1413+``.next_element`` y ``.previous_element``
1414+-----------------------------------------
1415+
1416+El atributo ``.next_element`` de una cadena o etiqueta apunta a cualquiera
1417+que fue analizado inmediatamente después. Podría ser igual que ``.next_sibling``,
1418+pero normalmente es drásticamente diferente.
1419+
1420+Aquí está la etiqueta final <a> en el documento de "Las tres hermanas".
1421+Su ``..next_sibling`` es una cadena: la terminación de la oración fue
1422+interrumpida por el comienzo de la etiqueta <a>.::
1423+
1424+ last_a_tag = soup.find("a", id="link3")
1425+ last_a_tag
1426+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
1427+
1428+ last_a_tag.next_sibling
1429+ # ';\nand they lived at the bottom of a well.'
1430+
1431+Pero el ``.next_element`` de esa etiqueta <a>, lo que fue analizado
1432+inmediatamente después de la etiqueta <a>, `no` es el resto de la
1433+oración: es la palabra "Tillie"::
1434+
1435+ last_a_tag.next_element
1436+ # 'Tillie'
1437+
1438+Esto se debe a que en el marcado original, la palabra "Tillie"
1439+aparece antes del punto y coma. El analizador se encontró con
1440+una etiqueta <a>, después la palabra "Tillie", entonces la etiqueta
1441+de cierre </a>, después el punto y coma y el resto de la oración.
1442+El punto y coma está al mismo nivel que la etiqueta <a>, pero
1443+la palabra "Tillie" se encontró primera.
1444+
1445+El atributo ``.previous_element`` es exactamente el opuesto
1446+de ``.next_element``. Apunta a cualquier elemento que
1447+fue analizado inmediatamente antes que este::
1448+
1449+ last_a_tag.previous_element
1450+ # ' and\n'
1451+ last_a_tag.previous_element.next_element
1452+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
1453+
1454+``.next_elements`` y ``.previous_elements``
1455+-------------------------------------------
1456+
1457+Ya te estarás haciendo a la idea. Puedes usar estos iteradores
1458+para moverte hacia delante y hacia atrás en el documento tal y como
1459+fue analizado::
1460+
1461+ for element in last_a_tag.next_elements:
1462+ print(repr(element))
1463+ # 'Tillie'
1464+ # ';\nand they lived at the bottom of a well.'
1465+ # '\n'
1466+ # <p class="story">...</p>
1467+ # '...'
1468+ # '\n'
1469+
1470+======================
1471+ Buscar en el árbol
1472+======================
1473+
1474+Beautiful Soup define una gran cantidad de métodos para buscar en
1475+el árbol analizado, pero todos son muy similares. Dedicaré mucho
1476+tiempo explicando los dos métodos más populares: ``find()`` y
1477+``find_all()``. Los otros métodos toman casi los mismos argumentos,
1478+así que los cubriré brevemente.
1479+
1480+De nuevo, usaré el documento de "Las tres hermanas" como ejemplo::
1481+
1482+ html_doc = """
1483+ <html><head><title>The Dormouse's story</title></head>
1484+ <body>
1485+ <p class="title"><b>The Dormouse's story</b></p>
1486+
1487+ <p class="story">Once upon a time there were three little sisters; and their names were
1488+ <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
1489+ <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
1490+ <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
1491+ and they lived at the bottom of a well.</p>
1492+
1493+ <p class="story">...</p>
1494+ """
1495+
1496+ from bs4 import BeautifulSoup
1497+ soup = BeautifulSoup(html_doc, 'html.parser')
1498+
1499+Empleando en un filtro un argumento como ``find_all()``, puedes
1500+"acercar" aquellas partes del documento en las que estés interesado.
1501+
1502+Tipos de filtros
1503+================
1504+
1505+Antes de entrar en detalle sobre ``find_all()`` y métodos similares,
1506+me gustaría mostrar ejemplos de diferentes filtros que puedes
1507+utilizar en estos métodos. Estos filtros aparecen una y otra vez a lo
1508+largo de la API. Puedes usarlos para filtrar basándote en el nombre de
1509+una etiqueta, en sus atributos, en el texto de una cadena, o en alguna
1510+combinación de estos.
1511+
1512+.. _a string:
1513+
1514+Una cadena
1515+----------
1516+
1517+El filtro más simple es una cadena. Pasa una cadena a un método de
1518+búsqueda y Beautiful Soup buscará un resultado para esa cadena
1519+exactamente. Este código encuentra todas las etiquetas <b> en el
1520+documento::
1521+
1522+ soup.find_all('b')
1523+ # [<b>The Dormouse's story</b>]
1524+
1525+Si pasas un cadena de *bytes*, Beautiful Soup asumirá que la cadena
1526+está codificada como UTF-8. Puedes evitar esto pasando una cadena
1527+Unicode.
1528+
1529+.. _a regular expression:
1530+
1531+Una expresión regular
1532+---------------------
1533+
1534+Si pasas un objeto que sea una expresión regular, Beautiful Soup filtrará
1535+mediante dicho expresión regular usando si su método ``search()``. Este
1536+código encuentra todas las etiquetas cuyo nombre empiece por la letra
1537+"b"; en este caso, las etiquetas <body> y <b>::
1538+
1539+ import re
1540+ for tag in soup.find_all(re.compile("^b")):
1541+ print(tag.name)
1542+ # body
1543+ # b
1544+
1545+Este código encuentra todas las etiquetas cuyo nombre contiene
1546+la letra 't'::
1547+
1548+ for tag in soup.find_all(re.compile("t")):
1549+ print(tag.name)
1550+ # html
1551+ # title
1552+
1553+.. _a list:
1554+
1555+Una lista
1556+---------
1557+
1558+Si pasas una lista, Beautiful Soup hará una búsqueda por cadenas
1559+con `cualquier` elemento en dicha lista. Este código encuentra
1560+todas las etiquetas <a> `y` todas las etiquetas <b>::
1561+
1562+ soup.find_all(["a", "b"])
1563+ # [<b>The Dormouse's story</b>,
1564+ # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1565+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1566+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1567+
1568+.. _the value True:
1569+
1570+``True``
1571+--------
1572+
1573+El valor ``True`` empareja todo lo que pueda. Este código encuentra
1574+``todas`` las etiquetas del documento, pero ninguna de las cadenas
1575+de texto::
1576+
1577+ for tag in soup.find_all(True):
1578+ print(tag.name)
1579+ # html
1580+ # head
1581+ # title
1582+ # body
1583+ # p
1584+ # b
1585+ # p
1586+ # a
1587+ # a
1588+ # a
1589+ # p
1590+
1591+.. a function:
1592+
1593+Una función
1594+-----------
1595+
1596+Si ninguna de las formas de búsqueda anteriores te sirven, define
1597+una función que tome un elemento como su único argumento. La función
1598+debería devolver ``True`` si el argumento se corresponde con lo indicado
1599+en la función, y ``Falso`` en cualquier otro caso.
1600+
1601+Esta es una función que devuelve ``True`` si una etiqueta tiene
1602+definida el atributo "class" pero no el atributo "id"::
1603+
1604+ def has_class_but_no_id(tag):
1605+ return tag.has_attr('class') and not tag.has_attr('id')
1606+
1607+Pasa esta función a ``find_all()`` y obtendrás todas las etiquetas
1608+<p>::
1609+
1610+ soup.find_all(has_class_but_no_id)
1611+ # [<p class="title"><b>The Dormouse's story</b></p>,
1612+ # <p class="story">Once upon a time there were…bottom of a well.</p>,
1613+ # <p class="story">...</p>]
1614+
1615+Esta función solo devuelve las etiquetas <p>. No obtiene las etiquetas
1616+<a>, porque esas etiquetas definen ambas "class" y "id". No devuelve
1617+etiquetas como <html> y <title> porque dichas etiquetas no definen
1618+"class".
1619+
1620+Si pasas una función para filtrar un atributo en específico como
1621+``href``, el argumento que se pasa a la función será el valor de
1622+dicho atributo, no toda la etiqueta. Esta es una función que
1623+encuentra todas las etiquetas <a> cuyo atributo ``href`` *no*
1624+empareja con una expresión regular::
1625+
1626+ import re
1627+ def not_lacie(href):
1628+ return href and not re.compile("lacie").search(href)
1629+
1630+ soup.find_all(href=not_lacie)
1631+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1632+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1633+
1634+La función puede ser tan complicada como la necesites. Esta es una
1635+función que devuelve ``True`` si una etiqueta está rodeada por
1636+objetos *string*::
1637+
1638+ from bs4 import NavigableString
1639+ def surrounded_by_strings(tag):
1640+ return (isinstance(tag.next_element, NavigableString)
1641+ and isinstance(tag.previous_element, NavigableString))
1642+
1643+ for tag in soup.find_all(surrounded_by_strings):
1644+ print(tag.name)
1645+ # body
1646+ # p
1647+ # a
1648+ # a
1649+ # a
1650+ # p
1651+
1652+Ahora ya estamos listos para entrar en detalle en los métodos
1653+de búsqueda.
1654+
1655+``find_all()``
1656+==============
1657+
1658+Firma del método: find_all(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`recursive
1659+<recursive>`, :ref:`string <string>`, :ref:`limit <limit>`, :ref:`**kwargs <kwargs>`)
1660+
1661+El método ``find_all()`` busca por los descendientes de una etiqueta y
1662+obtiene `todos` aquellos que casan con tus filtros. He mostrado varios
1663+ejemplos en `Tipos de filtros`_, pero aquí hay unos cuantos más::
1664+
1665+ soup.find_all("title")
1666+ # [<title>The Dormouse's story</title>]
1667+
1668+ soup.find_all("p", "title")
1669+ # [<p class="title"><b>The Dormouse's story</b></p>]
1670+
1671+ soup.find_all("a")
1672+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1673+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1674+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1675+
1676+ soup.find_all(id="link2")
1677+ # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
1678+
1679+ import re
1680+ soup.find(string=re.compile("sisters"))
1681+ # 'Once upon a time there were three little sisters; and their names were\n'
1682+
1683+Algunos de estos deberían ser familiares, pero otros son nuevos.
1684+¿Qué significa pasar un valor para ``string``, o ``id``? ¿Por qué
1685+``find_all("p", "title")`` encuentra una etiqueta <p> con la clase
1686+CSS "title"? Echemos un vistazo a los argumentos de ``find_all()``.
1687+
1688+.. _name:
1689+
1690+El argumento ``name``
1691+---------------------
1692+
1693+Pasa un valor para ``name`` y notarás que Beautiful Soup solo
1694+considera etiquetas con ciertos nombres. Las cadenas de texto se
1695+ignorarán, como aquellas etiquetas cuyo nombre no emparejen.
1696+
1697+Este es el uso más simple::
1698+
1699+ soup.find_all("title")
1700+ # [<title>The Dormouse's story</title>]
1701+
1702+Recuerda de `Tipos de filtros`_ que el valor para ``name`` puede ser
1703+`una cadena`_, `una expresión regular`_, `una lista`_, `una función`_,
1704+o el valor `True`_.
1705+
1706+.. _kwargs:
1707+
1708+El argumento palabras-clave
1709+---------------------------
1710+
1711+Cualquier argumento que no se reconozca se tomará como un filtro para alguno
1712+de los atributos de una etiqueta. Si pasas un valor para un argumento llamado
1713+``id``, Beautiful Soup filtrará el atributo 'id' de cada una de las etiquetas::
1714+
1715+ soup.find_all(id='link2')
1716+ # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
1717+
1718+Si pasas un valor para ``href``, Beautiful Soup filtrará
1719+el atributo ``href`` de cada uno de las etiquetas::
1720+
1721+ soup.find_all(href=re.compile("elsie"))
1722+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
1723+
1724+Puedes filtrar un atributo basándote en `una cadena`_,
1725+`una expresión regular`_, `una lista`_, `una función`_, o el valor
1726+`True`_.
1727+
1728+Este código busca todas las etiquetas cuyo atributo ``id`` tiene
1729+un valor, sin importar qué valor es::
1730+
1731+ soup.find_all(id=True)
1732+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1733+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1734+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1735+
1736+Puedes filtrar varios atributos al mismo tiempo pasando más de un argumento
1737+palabra-clave::
1738+
1739+ soup.find_all(href=re.compile("elsie"), id='link1')
1740+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
1741+
1742+Algunos atributos, como los atributos data-* en HTML5, tienen nombres que
1743+no pueden ser usados como nombres de argumentos palabra-clave::
1744+
1745+ data_soup = BeautifulSoup('<div data-foo="value">foo!</div>', 'html.parser')
1746+ data_soup.find_all(data-foo="value")
1747+ # SyntaxError: keyword can't be an expression
1748+
1749+Puedes usar estos atributos en búsquedas insertándolos en un diccionario
1750+y pasándolo a ``find_all()`` como el argumento ``attrs``::
1751+
1752+ data_soup.find_all(attrs={"data-foo": "value"})
1753+ # [<div data-foo="value">foo!</div>]
1754+
1755+No puedes usar un argumento palabra-clave para buscar por el nombre
1756+HTML de un elemento, porque BeautifulSoup usa el argumento ``name``
1757+para guardar el nombre de la etiqueta. En lugar de esto, puedes
1758+darle valor a 'name' en el argumento ``attrs``::
1759+
1760+ name_soup = BeautifulSoup('<input name="email"/>', 'html.parser')
1761+ name_soup.find_all(name="email")
1762+ # []
1763+ name_soup.find_all(attrs={"name": "email"})
1764+ # [<input name="email"/>]
1765+
1766+.. _attrs:
1767+
1768+Buscando por clase CSS
1769+----------------------
1770+
1771+Es muy útil para buscar una etiqueta que tenga una clase CSS específica,
1772+pero el nombre del atributo CSS, "class", es una palabra reservada de
1773+Python. Usar ``class`` como argumento ocasionaría un error sintáctico.
1774+Desde Beautiful Soup 4.1.2, se puede buscar por una clase CSS usando
1775+el argumento palabra-clave ``class_``::
1776+
1777+ soup.find_all("a", class_="sister")
1778+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1779+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1780+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1781+
1782+Como con cualquier argumento palabra-clave, puede pasar una cadena
1783+de caracteres a ``class_``, una expresión regular, una función, o
1784+``True``::
1785+
1786+ soup.find_all(class_=re.compile("itl"))
1787+ # [<p class="title"><b>The Dormouse's story</b></p>]
1788+
1789+ def has_six_characters(css_class):
1790+ return css_class is not None and len(css_class) == 6
1791+
1792+ soup.find_all(class_=has_six_characters)
1793+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1794+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1795+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1796+
1797+:ref:`Recuerda <multivalue>` que una sola etiqueta puede tener varios
1798+valores para su atributo "class". Cuando se busca por una etiqueta
1799+que case una cierta clase CSS, se está intentando emparejar por
1800+`cualquiera` de sus clases CSS::
1801+
1802+ css_soup = BeautifulSoup('<p class="body strikeout"></p>', 'html.parser')
1803+ css_soup.find_all("p", class_="strikeout")
1804+ # [<p class="body strikeout"></p>]
1805+
1806+ css_soup.find_all("p", class_="body")
1807+ # [<p class="body strikeout"></p>]
1808+
1809+Puedes también buscar por la cadena de caracteres exacta del atributo
1810+``class``::
1811+
1812+ css_soup.find_all("p", class_="body strikeout")
1813+ # [<p class="body strikeout"></p>]
1814+
1815+Pero buscar por variantes de la cadena de caracteres no funcionará::
1816+
1817+ css_soup.find_all("p", class_="strikeout body")
1818+ # []
1819+
1820+Si quieres buscar por las etiquetas que casen dos o más clases CSS,
1821+deberías usar un selector CSS::
1822+
1823+ css_soup.select("p.strikeout.body")
1824+ # [<p class="body strikeout"></p>]
1825+
1826+En versiones antiguas de Beautiful Soup, que no soportan el
1827+atajo ``class_``, puedes usar el truco del ``attrs`` mencionado
1828+arriba. Crea un diccionario cuyo valor para "class" sea la
1829+cadena de caracteres (o expresión regular, o lo que sea) que
1830+quieras buscar::
1831+
1832+ soup.find_all("a", attrs={"class": "sister"})
1833+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1834+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
1835+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1836+
1837+.. _string:
1838+
1839+El argumento ``string``
1840+-----------------------
1841+
1842+Con ``string`` puedes buscar por cadenas de caracteres en vez de
1843+etiquetas. Como con ``name`` y argumentos palabras-clave, puedes
1844+pasar `una cadena`_, `una expresión regular`_, `una lista`_, `una
1845+función`_, o el valor `True`_.
1846+Aquí hay algunos ejemplos::
1847+
1848+ soup.find_all(string="Elsie")
1849+ # ['Elsie']
1850+
1851+ soup.find_all(string=["Tillie", "Elsie", "Lacie"])
1852+ # ['Elsie', 'Lacie', 'Tillie']
1853+
1854+ soup.find_all(string=re.compile("Dormouse"))
1855+ # ["The Dormouse's story", "The Dormouse's story"]
1856+
1857+ def is_the_only_string_within_a_tag(s):
1858+ """Return True if this string is the only child of its parent tag."""
1859+ return (s == s.parent.string)
1860+
1861+ soup.find_all(string=is_the_only_string_within_a_tag)
1862+ # ["The Dormouse's story", "The Dormouse's story", 'Elsie', 'Lacie', 'Tillie', '...']
1863+
1864+
1865+Aunque ``string`` es para encontrar cadenas, puedes combinarlo
1866+con argumentos que permitan buscar etiquetas: Beautiful Soup
1867+encontrará todas las etiquetas cuyo ``.string`` case con tu valor
1868+para ``string``. Este código encuentra las etiquetas <a> cuyo
1869+``.string`` es "Elsie"::
1870+
1871+ soup.find_all("a", string="Elsie")
1872+ # [<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>]
1873+
1874+El argumento ``string`` es nuevo en Beautiful Soup 4.4.0. En versiones
1875+anteriores se llamaba ``text``::
1876+
1877+ soup.find_all("a", text="Elsie")
1878+ # [<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>]
1879+
1880+.. _limit:
1881+
1882+El argumento``limit``
1883+---------------------
1884+
1885+``find_all()`` devuelve todas las etiquetas y cadenas que emparejan
1886+con tus filtros. Esto puede tardar un poco si el documento es grande.
1887+Si no necesitas `todos` los resultados, puedes pasar un número para
1888+``limit``. Esto funciona tal y como lo hace la palabra LIMIT en SQL.
1889+Indica a Beautiful Soup dejar de obtener resultados después de
1890+haber encontrado un cierto número.
1891+
1892+Hay tres enlaces en el documento de "Las tres hermanas", pero este
1893+código tan solo obtiene los dos primeros::
1894+
1895+ soup.find_all("a", limit=2)
1896+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
1897+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
1898+
1899+.. _recursive:
1900+
1901+El argumento ``recursive``
1902+--------------------------
1903+
1904+Si llamas a ``mytag.find_all()``, Beautiful Soup examinará todos los
1905+descendientes de ``mytag``: sus hijos, los hijos de sus hijos, y
1906+así sucesivamente. Si solo quieres que Beautiful Soup considere
1907+hijos directos, puedes pasar ``recursive=False``. Observa las
1908+diferencias aquí::
1909+
1910+ soup.html.find_all("title")
1911+ # [<title>The Dormouse's story</title>]
1912+
1913+ soup.html.find_all("title", recursive=False)
1914+ # []
1915+
1916+Aquí está esa parte del documento::
1917+
1918+ <html>
1919+ <head>
1920+ <title>
1921+ The Dormouse's story
1922+ </title>
1923+ </head>
1924+ ...
1925+
1926+La etiqueta <title> va después de la etiqueta <html>, pero no está
1927+`directamente` debajo de la etiqueta <html>: la etiqueta <head>
1928+está en medio de ambas. Beautiful Soup encuentra la etiqueta <title> cuando
1929+se permite observar todos los descendientes de la etiqueta <html>,
1930+pero cuando ``recursive=False`` restringe a los hijos directos
1931+de la etiqueta <html>, no se encuentra nada.
1932+
1933+Beautiful Soup ofrece mucho métodos de análisis del árbol (descritos
1934+más adelante), y la mayoría toman los mismos argumentos que ``find_all()``:
1935+``name``, ``attrs``, ``string``, ``limit``, y los argumentos
1936+palabras-clave. Pero el argumento ``recursive`` es diferente:
1937+``find_all()`` y ``find()`` son los únicos métodos que lo soportan.
1938+Pasar ``recursive=False`` en un método como ``find_parents()`` no sería
1939+muy útil.
1940+
1941+Llamar a una etiqueta es como llamar a ``find_all()``
1942+=====================================================
1943+
1944+Como ``find_all()`` es el método más popular en la API de búsqueda
1945+de Beautiful Soup, puedes usar un atajo para usarlo. Si utilizas
1946+el objeto :py:class:`BeautifulSoup` o un objeto :py:class:`Tag`
1947+como si fuesen una función, entonces es lo mismo que llamar a
1948+``find_all()`` en esos objetos. Estos dos líneas de código son
1949+equivalentes::
1950+
1951+ soup.find_all("a")
1952+ soup("a")
1953+
1954+Estas dos líneas de código son también equivalentes::
1955+
1956+ soup.title.find_all(string=True)
1957+ soup.title(string=True)
1958+
1959+``find()``
1960+==========
1961+
1962+Firma del método: find(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`recursive
1963+<recursive>`, :ref:`string <string>`, :ref:`**kwargs <kwargs>`)
1964+
1965+El método ``find_all()`` examina todo el documento buscando por
1966+resultados, pero a veces solo quieres encontrar un resultado.
1967+Si sabes que un documento solo tiene una etiqueta <body>, es una
1968+pérdida de tiempo examinar todo el documento buscando más
1969+emparejamientos. En lugar de pasar ``limit=1`` siempre que se llame
1970+a ``find_all(), puedes usar el método ``find()``. Estas dos líneas
1971+de código son `casi` equivalentes::
1972+
1973+ soup.find_all('title', limit=1)
1974+ # [<title>The Dormouse's story</title>]
1975+
1976+ soup.find('title')
1977+ # <title>The Dormouse's story</title>
1978+
1979+La única diferencia es que ``find_all()`` devuelve una lista
1980+conteniendo un resultado, y ``find()`` devuelve solo el resultado.
1981+
1982+Si ``find_all()`` no encuentra nada, devuelve una lista vacía. Si
1983+``find()`` no encuentra nada, devuelve ``None``::
1984+
1985+ print(soup.find("nosuchtag"))
1986+ # None
1987+
1988+¿Recuerdas el truco de ``soup.head.title`` de `Navegar usando nombres
1989+de etiquetas`_? Ese truco funciona porque se llama repetidamente a
1990+``find()``::
1991+
1992+ soup.head.title
1993+ # <title>The Dormouse's story</title>
1994+
1995+ soup.find("head").find("title")
1996+ # <title>The Dormouse's story</title>
1997+
1998+``find_parents()`` y ``find_parent()``
1999+======================================
2000+
2001+Firma del método: find_parents(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`limit <limit>`, :ref:`**kwargs <kwargs>`)
2002+
2003+Firma del método: find_parent(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`**kwargs <kwargs>`)
2004+
2005+He pasado bastante tiempo cubriendo ``find_all()`` y ``find()``.
2006+La API de Beautiful Soup define otros diez métodos para buscar por
2007+el árbol, pero no te asustes. Cinco de estos métodos son básicamente
2008+iguales a ``find_all()``, y los otros cinco son básicamente
2009+iguales a ``find()``. La única diferencia reside en qué partes del
2010+árbol buscan.
2011+
2012+Primero consideremos ``find_parents()`` y ``find_paren()``. Recuerda
2013+que ``find_all()`` y ``find()`` trabajan bajando por el árbol,
2014+examinando a los descendientes de una etiqueta. Estos métodos realizan
2015+lo contrario: trabajan `subiendo` por el árbol, buscando a las madres
2016+de las etiquetas (o cadenas). Probémoslos, empezando por una cadena
2017+de caracteres que esté bien enterrada en el documento de "Las tres
2018+hermanas"::
2019+
2020+ a_string = soup.find(string="Lacie")
2021+ a_string
2022+ # 'Lacie'
2023+
2024+ a_string.find_parents("a")
2025+ # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
2026+
2027+ a_string.find_parent("p")
2028+ # <p class="story">Once upon a time there were three little sisters; and their names were
2029+ # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
2030+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
2031+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
2032+ # and they lived at the bottom of a well.</p>
2033+
2034+ a_string.find_parents("p", class_="title")
2035+ # []
2036+
2037+Una de la tres etiquetas <a> is la madre directa de la cadena
2038+en cuestión, así que nuestra búsqueda la encuentra. Una de las
2039+tres etiquetas <p> es una madre indirecta de la cadena, y nuestra
2040+búsqueda también la encuentra. Hay una etiqueta <p> con la clase
2041+CSS "title" `en algún sitio` del documento, pero no en ninguno
2042+de las madres de la cadena, así que no podemos encontrarla con
2043+``find_parents()``.
2044+
2045+Puedes haber deducido la conexión entre ``find_parent()`` y
2046+``find_parents()``, y los atributos `.parent`_ y `.parents`_
2047+mencionados anteriormente. La conexión es muy fuerte. Estos
2048+métodos de búsqueda realmente usan ``.parents`` para iterar
2049+sobre todas las madres, y comprobar cada una con el filtro
2050+provisto para ver si emparejan.
2051+
2052+``find_next_siblings()`` y ``find_next_sibling()``
2053+==================================================
2054+
2055+Firma del método: find_next_siblings(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`limit <limit>`, :ref:`**kwargs <kwargs>`)
2056+
2057+Firma del método: find_next_sibling(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`**kwargs <kwargs>`)
2058+
2059+Estos métodos usan :ref:`next_siblings <sibling-generators>`
2060+para iterar sobre el resto de los hermanos de un elemento en el
2061+árbol. El método ``find_next_siblings()`` devuelve todos los
2062+hermanos que casen, y ``find_next_sibling()`` solo devuelve
2063+el primero de ellos::
2064+
2065+ first_link = soup.a
2066+ first_link
2067+ # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
2068+
2069+ first_link.find_next_siblings("a")
2070+ # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
2071+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
2072+
2073+ first_story_paragraph = soup.find("p", "story")
2074+ first_story_paragraph.find_next_sibling("p")
2075+ # <p class="story">...</p>
2076+
2077+``find_previous_siblings()`` y ``find_previous_sibling()``
2078+==========================================================
2079+
2080+Firma del método: find_previous_siblings(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`limit <limit>`, :ref:`**kwargs <kwargs>`)
2081+
2082+Firma del método: find_previous_sibling(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`**kwargs <kwargs>`)
2083+
2084+Estos métodos emplean :ref:`.previous_siblings <sibling-generators>` para iterar sobre
2085+los hermanos de un elemento que les precede en el árbol. El método
2086+``find_previous_siblings()`` devuelve todos los hermanos que emparejan, y
2087+``find_previous_sibling()`` solo devuelve el primero de ellos::
2088+
2089+ last_link = soup.find("a", id="link3")
2090+ last_link
2091+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
2092+
2093+ last_link.find_previous_siblings("a")
2094+ # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
2095+ # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
2096+
2097+ first_story_paragraph = soup.find("p", "story")
2098+ first_story_paragraph.find_previous_sibling("p")
2099+ # <p class="title"><b>The Dormouse's story</b></p>
2100+
2101+
2102+``find_all_next()`` y ``find_next()``
2103+=====================================
2104+
2105+Firma del método: find_all_next(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`limit <limit>`, :ref:`**kwargs <kwargs>`)
2106+
2107+Firma del método: find_next(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`**kwargs <kwargs>`)
2108+
2109+Estos métodos usan :ref:`.next_elements <element-generators>` para
2110+iterar sobre cualesquiera etiquetas y cadenas que vayan después
2111+de ella en el documento. El método ``find_all_next()`` devuelve
2112+todos los resultados, y ``find_next()`` solo devuelve el primero::
2113+
2114+ first_link = soup.a
2115+ first_link
2116+ # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
2117+
2118+ first_link.find_all_next(string=True)
2119+ # ['Elsie', ',\n', 'Lacie', ' and\n', 'Tillie',
2120+ # ';\nand they lived at the bottom of a well.', '\n', '...', '\n']
2121+
2122+ first_link.find_next("p")
2123+ # <p class="story">...</p>
2124+
2125+En el primer ejemplo, la cadena "Elsie" apareció, aunque estuviese
2126+contenida en la etiqueta <a> desde la que comenzamos. En el segundo
2127+ejemplo, la última etiqueta <p> en el documento apareció, aunque no
2128+esté en la misma parte del árbol que la etiqueta <a> desde la que
2129+comenzamos. Para estos métodos, todo lo que importa es que un
2130+elemento cumple con el filtro, y que aparezca en el documento
2131+después del elemento inicial.
2132+
2133+``find_all_previous()`` y ``find_previous()``
2134+=============================================
2135+
2136+Firma del método: find_all_previous(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`limit <limit>`, :ref:`**kwargs <kwargs>`)
2137+
2138+Firma del método: find_previous(:ref:`name <name>`, :ref:`attrs <attrs>`, :ref:`string <string>`, :ref:`**kwargs <kwargs>`)
2139+
2140+Estos métodos usan :ref:`.previous_elements <element-generators>`
2141+para iterar sobre las etiquetas y cadenas que iban antes en el
2142+documento. El método ``find_all_previous()`` devuelve todos los
2143+resultados, y ``find_previous()`` solo devuelve el primero::
2144+
2145+ first_link = soup.a
2146+ first_link
2147+ # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
2148+
2149+ first_link.find_all_previous("p")
2150+ # [<p class="story">Once upon a time there were three little sisters; ...</p>,
2151+ # <p class="title"><b>The Dormouse's story</b></p>]
2152+
2153+ first_link.find_previous("title")
2154+ # <title>The Dormouse's story</title>
2155+
2156+La llamada a ``find_all_previous("p")`` encontró el primer
2157+párrafo en el documento (el que tiene la clase="title"), pero
2158+también encuentra el segundo párrafo, la etiqueta <p> que
2159+contiene la etiqueta <a> con la que comenzamos. Esto no debería
2160+ser demasiado sorprendente: estamos buscando todas las etiquetas
2161+que aparecen en el documento después de la etiqueta con la que se
2162+comienza. Una etiqueta <p> que contiene una <a> debe aparecer
2163+antes de la etiqueta <a> que contiene.
2164+
2165+Selectores CSS mediante la propiedad ``.css``
2166+=============================================
2167+
2168+Los objetos :py:class:`BeautifulSoup` y :py:class:`Tag` soportan los selectores
2169+CSS a través de su atributo ``.css``. El paquete `Soup Sieve <https://facelessuser.github.io/soupsieve/>`_,
2170+disponible a través de PyPI como ``soupsieve``, gestiona la implementación real
2171+del selector. Si instalaste Beautiful Soup mediante ``pip``, Soup Sieve se
2172+instaló al mismo tiempo, así que no tienes que hacer nada adicional.
2173+
2174+La documentación de Soup Sieve lista `todos los selectores CSS soportados
2175+actualmente <https://facelessuser.github.io/soupsieve/selectors/>`_, pero
2176+estos son algunos de los básicos. Puedes encontrar etiquetas::
2177+
2178+ soup.css.select("title")
2179+ # [<title>The Dormouse's story</title>]
2180+
2181+ soup.css.select("p:nth-of-type(3)")
2182+ # [<p class="story">...</p>]
2183+
2184+Encontrar etiquetas dentro de otras etiquetas::
2185+
2186+ soup.css.select("body a")
2187+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
2188+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
2189+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
2190+
2191+ soup.css.select("html head title")
2192+ # [<title>The Dormouse's story</title>]
2193+
2194+Encontrar etiquetas `directamente` después de otras etiquetas::
2195+
2196+ soup.css.select("head > title")
2197+ # [<title>The Dormouse's story</title>]
2198+
2199+ soup.css.select("p > a")
2200+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
2201+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
2202+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
2203+
2204+ soup.css.select("p > a:nth-of-type(2)")
2205+ # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
2206+
2207+ soup.css.select("p > #link1")
2208+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
2209+
2210+ soup.css.select("body > a")
2211+ # []
2212+
2213+Encontrar los hijos de etiquetas::
2214+
2215+ soup.css.select("#link1 ~ .sister")
2216+ # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
2217+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
2218+
2219+ soup.css.select("#link1 + .sister")
2220+ # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
2221+
2222+Encontrar etiquetas por su clase CSS::
2223+
2224+ soup.css.select(".sister")
2225+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
2226+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
2227+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
2228+
2229+ soup.css.select("[class~=sister]")
2230+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
2231+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
2232+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
2233+
2234+Encontrar etiquetas por su ID::
2235+
2236+ soup.css.select("#link1")
2237+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
2238+
2239+ soup.css.select("a#link2")
2240+ # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
2241+
2242+Encontrar etiquetas que casen con cualquier selector que estés en una
2243+lista de selectores::
2244+
2245+ soup.css.select("#link1,#link2")
2246+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
2247+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
2248+
2249+Comprobar la existencia de un atributo::
2250+
2251+ soup.css.select('a[href]')
2252+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
2253+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
2254+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
2255+
2256+Encontrar etiquetas por el valor de un atributo::
2257+
2258+ soup.css.select('a[href="http://example.com/elsie"]')
2259+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
2260+
2261+ soup.css.select('a[href^="http://example.com/"]')
2262+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
2263+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
2264+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
2265+
2266+ soup.css.select('a[href$="tillie"]')
2267+ # [<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
2268+
2269+ soup.css.select('a[href*=".com/el"]')
2270+ # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
2271+
2272+Hay también un método llamado ``select_one()``, que encuentra solo
2273+la primera etiqueta que case con un selector::
2274+
2275+ soup.css.select_one(".sister")
2276+ # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
2277+
2278+Por conveniencia, puedes llamar a ``select()`` y ``select_one()`` sobre
2279+el objeto :py:class:`BeautifulSoup` o :py:class:`Tag`, omitiendo la
2280+propiedad ``.css``::
2281+
2282+ soup.select('a[href$="tillie"]')
2283+ # [<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
2284+
2285+ soup.select_one(".sister")
2286+ # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
2287+
2288+El soporte de selectores CSS es conveniente para personas que ya conocen
2289+la sintaxis de los selectores CSS. Puedes hacer todo esto con la API
2290+de Beautiful Soup. Si todo lo que necesitas son los selectores CSS, deberías
2291+saltarte Beautiful Soup y analizar el documento con ``lxml``: es mucho más
2292+rápido. Pero Soup Sieve te permite `combinar` selectores CSS con la API
2293+de Beautiful Soup.
2294+
2295+Características avanzadas de Soup Sieve
2296+---------------------------------------
2297+
2298+Soup Sieve ofrece una API más amplia más allá de los métodos ``select()``
2299+y ``select_one()``, y puedes acceder a casi toda esa API a través del
2300+atributo ``.css`` de :py:class:`Tag` o :py:class:`Beautiful Soup`. Lo que
2301+sigue es solo una lista de los métodos soportados; ve a `la documentación de
2302+Soup Sieve <https://facelessuser.github.io/soupsieve/>`_ para la documentación
2303+completa.
2304+
2305+El método ``iselect()`` funciona igualmente que ``select()``, solo que
2306+devuelve un generador en vez de una lista::
2307+
2308+ [tag['id'] for tag in soup.css.iselect(".sister")]
2309+ # ['link1', 'link2', 'link3']
2310+
2311+El método ``closest()`` devuelve la madre más cercana de una :py:class:`Tag` dada
2312+que case con un selector CSS, similar al método ``find_parent()`` de
2313+Beautiful Soup::
2314+
2315+ elsie = soup.css.select_one(".sister")
2316+ elsie.css.closest("p.story")
2317+ # <p class="story">Once upon a time there were three little sisters; and their names were
2318+ # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
2319+ # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
2320+ # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
2321+ # and they lived at the bottom of a well.</p>
2322+
2323+El método ``match()`` devuelve un booleano dependiendo de si
2324+una :py:class:`Tag` específica casa con un selector o no::
2325+
2326+ # elsie.css.match("#link1")
2327+ True
2328+
2329+ # elsie.css.match("#link2")
2330+ False
2331+
2332+El método ``filter()`` devuelve un subconjunto de los hijos directos
2333+de una etiqueta que casen con un selector::
2334+
2335+ [tag.string for tag in soup.find('p', 'story').css.filter('a')]
2336+ # ['Elsie', 'Lacie', 'Tillie']
2337+
2338+El método ``escape()`` formatea los identificadores CSS que de otra
2339+forma serían inválidos::
2340+
2341+ soup.css.escape("1-strange-identifier")
2342+ # '\\31 -strange-identifier'
2343+
2344+Espacios de nombres en selectores CSS
2345+-------------------------------------
2346+Si has analizado XML que define espacios de nombres, puedes usarlos
2347+en selectores CSS::
2348+
2349+ from bs4 import BeautifulSoup
2350+ xml = """<tag xmlns:ns1="http://namespace1/" xmlns:ns2="http://namespace2/">
2351+ <ns1:child>I'm in namespace 1</ns1:child>
2352+ <ns2:child>I'm in namespace 2</ns2:child>
2353+ </tag> """
2354+ namespace_soup = BeautifulSoup(xml, "xml")
2355+
2356+ namespace_soup.css.select("child")
2357+ # [<ns1:child>I'm in namespace 1</ns1:child>, <ns2:child>I'm in namespace 2</ns2:child>]
2358+
2359+ namespace_soup.css.select("ns1|child")
2360+ # [<ns1:child>I'm in namespace 1</ns1:child>]
2361+
2362+Beautiful Soup intenta usar prefijos de espacios de nombres que tengan
2363+sentido basándose en lo que vio al analizar el documento, pero siempre
2364+puedes indicar tu propio diccionario de abreviaciones::
2365+
2366+ namespaces = dict(first="http://namespace1/", second="http://namespace2/")
2367+ namespace_soup.css.select("second|child", namespaces=namespaces)
2368+ # [<ns1:child>I'm in namespace 2</ns1:child>]
2369+
2370+Historia del soporte de selectores CSS
2371+--------------------------------------
2372+
2373+La propiedad ``.css`` fue añadida en Beautiful Soup 4.12.0. Anterior a esta,
2374+solo los métodos convenientes ``.select()`` y ``select_one()`` se
2375+soportaban.
2376+
2377+La integración de Soup Sieve fue añadida en Beautiful Soup 4.7.0. Versiones
2378+anteriores tenían el método ``.select()``, pero solo los selectores CSS
2379+más comunes eran admitidos.
2380+
2381+
2382+====================
2383+ Modificar el árbol
2384+====================
2385+
2386+La mayor fortaleza de Beautiful Soup reside en buscar en el árbol
2387+analizado, pero puedes también modificar el árbol y escribir tus
2388+cambios como un nuevo documento HTML o XML.
2389+
2390+Cambiar nombres de etiquetas y atributos
2391+========================================
2392+
2393+Cubrí esto anteriormente, en :py:class:`Tag.attrs`, pero vale la pena
2394+repetirlo. Puedes renombrar una etiqueta, cambiar el valor de sus
2395+atributos, añadir nuevos atributos, y eliminar atributos::
2396+
2397+ soup = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'html.parser')
2398+ tag = soup.b
2399+
2400+ tag.name = "blockquote"
2401+ tag['class'] = 'verybold'
2402+ tag['id'] = 1
2403+ tag
2404+ # <blockquote class="verybold" id="1">Extremely bold</blockquote>
2405+
2406+ del tag['class']
2407+ del tag['id']
2408+ tag
2409+ # <blockquote>Extremely bold</blockquote>
2410+
2411+Modificar ``.string``
2412+=====================
2413+
2414+Si quieres establecer el ``.string`` de una etiqueta a una nueva cadena de
2415+caracteres, los contenidos de la etiqueta se pueden reemplazar con esa cadena::
2416+
2417+ markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
2418+ soup = BeautifulSoup(markup, 'html.parser')
2419+
2420+ tag = soup.a
2421+ tag.string = "New link text."
2422+ tag
2423+ # <a href="http://example.com/">New link text.</a>
2424+
2425+Ten cuidado: si una etiqueta contiene otras, ellas y todo su contenido
2426+serán destruidos.
2427+
2428+``append()``
2429+============
2430+
2431+Puedes añadir al contenido de una etiqueta con ``Tag.append()``.
2432+Funciona como llamar a ``.append()`` en una lista de Python::
2433+
2434+ soup = BeautifulSoup("<a>Foo</a>", 'html.parser')
2435+ soup.a.append("Bar")
2436+
2437+ soup
2438+ # <a>FooBar</a>
2439+ soup.a.contents
2440+ # ['Foo', 'Bar']
2441+
2442+``extend()``
2443+============
2444+
2445+Desde Beautiful Soup 4.7.0, :py:class:`Tag` también soporta un método
2446+llamado ``.extend()``, el cual añade todos los elementos de una lista
2447+a una :py:class:`Tag`, en orden::
2448+
2449+ soup = BeautifulSoup("<a>Soup</a>", 'html.parser')
2450+ soup.a.extend(["'s", " ", "on"])
2451+
2452+ soup
2453+ # <a>Soup's on</a>
2454+ soup.a.contents
2455+ # ['Soup', ''s', ' ', 'on']
2456+
2457+``NavigableString()`` y ``.new_tag()``
2458+======================================
2459+
2460+Si necesitas añadir una cadena a un documento, sin problema--puedes
2461+pasar una cadena de Python a ``append()``, o puedes llamar al constructor
2462+de :py:class:`NavigableString`::
2463+
2464+ from bs4 import NavigableString
2465+ soup = BeautifulSoup("<b></b>", 'html.parser')
2466+ tag = soup.b
2467+ tag.append("Hello")
2468+ new_string = NavigableString(" there")
2469+ tag.append(new_string)
2470+ tag
2471+ # <b>Hello there.</b>
2472+ tag.contents
2473+ # ['Hello', ' there']
2474+
2475+Si quieres crear un comentario o cualquier otra subclase
2476+de :py:class:`NavigableString`, solo llama al constructor::
2477+
2478+ from bs4 import Comment
2479+ new_comment = Comment("Nice to see you.")
2480+ tag.append(new_comment)
2481+ tag
2482+ # <b>Hello there<!--Nice to see you.--></b>
2483+ tag.contents
2484+ # ['Hello', ' there', 'Nice to see you.']
2485+
2486+`(Esto es una nueva característica en Beautiful Soup 4.4.0.)`
2487+
2488+¿Qué ocurre si necesitas crear una etiqueta totalmente nueva? La mejor
2489+solución es llamar al método de construcción (`factory method`)
2490+``BeautifulSoup.new_tag()``::
2491+
2492+ soup = BeautifulSoup("<b></b>", 'html.parser')
2493+ original_tag = soup.b
2494+
2495+ new_tag = soup.new_tag("a", href="http://www.example.com")
2496+ original_tag.append(new_tag)
2497+ original_tag
2498+ # <b><a href="http://www.example.com"></a></b>
2499+
2500+ new_tag.string = "Link text."
2501+ original_tag
2502+ # <b><a href="http://www.example.com">Link text.</a></b>
2503+
2504+Solo el primer argumento, el nombre de la etiqueta, es
2505+obligatorio.
2506+
2507+``insert()``
2508+============
2509+
2510+``Tag.insert()`` es justo como ``Tag.append()``, excepto que el nuevo
2511+elemento no necesariamente va al final del ``.contents`` de su madre.
2512+Se insertará en la posición numérica que le hayas indicado. Funciona
2513+como ``.insert()`` es una lista de Python::
2514+
2515+ markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
2516+ soup = BeautifulSoup(markup, 'html.parser')
2517+ tag = soup.a
2518+
2519+ tag.insert(1, "but did not endorse ")
2520+ tag
2521+ # <a href="http://example.com/">I linked to but did not endorse <i>example.com</i></a>
2522+ tag.contents
2523+ # ['I linked to ', 'but did not endorse', <i>example.com</i>]
2524+
2525+``insert_before()`` y ``insert_after()``
2526+========================================
2527+
2528+El método ``insert_before()`` inserta etiquetas o cadenas
2529+inmediatamente antes de algo en el árbol analizado::
2530+
2531+ soup = BeautifulSoup("<b>leave</b>", 'html.parser')
2532+ tag = soup.new_tag("i")
2533+ tag.string = "Don't"
2534+ soup.b.string.insert_before(tag)
2535+ soup.b
2536+ # <b><i>Don't</i>leave</b>
2537+
2538+El método ``insert_after()`` inserta etiquetas o cadenas
2539+inmediatamente después de algo en el árbol analizado::
2540+
2541+ div = soup.new_tag('div')
2542+ div.string = 'ever'
2543+ soup.b.i.insert_after(" you ", div)
2544+ soup.b
2545+ # <b><i>Don't</i> you <div>ever</div> leave</b>
2546+ soup.b.contents
2547+ # [<i>Don't</i>, ' you', <div>ever</div>, 'leave']
2548+
2549+``clear()``
2550+===========
2551+
2552+``Tag.clear()`` quita los contenidos de una etiqueta::
2553+
2554+ markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
2555+ soup = BeautifulSoup(markup, 'html.parser')
2556+ tag = soup.a
2557+
2558+ tag.clear()
2559+ tag
2560+ # <a href="http://example.com/"></a>
2561+
2562+``extract()``
2563+=============
2564+
2565+``PageElement.extract()`` elimina una etiqueta o una cadena de caracteres
2566+del árbol. Devuelve la etiqueta o la cadena que fue extraída::
2567+
2568+ markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
2569+ soup = BeautifulSoup(markup, 'html.parser')
2570+ a_tag = soup.a
2571+
2572+ i_tag = soup.i.extract()
2573+
2574+ a_tag
2575+ # <a href="http://example.com/">I linked to</a>
2576+
2577+ i_tag
2578+ # <i>example.com</i>
2579+
2580+ print(i_tag.parent)
2581+ # None
2582+
2583+En este punto tienes realmente dos árboles analizados: uno anclado en el
2584+objeto :py:class:`BeautifulSoup` que usaste para analizar el documento, y
2585+uno anclado en la etiqueta que fue extraída. Puedes llamar a ``extract``
2586+en el hijo del elemento que extrajiste::
2587+
2588+ my_string = i_tag.string.extract()
2589+ my_string
2590+ # 'example.com'
2591+
2592+ print(my_string.parent)
2593+ # None
2594+ i_tag
2595+ # <i></i>
2596+
2597+
2598+``decompose()``
2599+===============
2600+
2601+``Tag.decompose()`` quita una etiqueta del árbol, y luego `lo destruye
2602+completamente y su contenido también`::
2603+
2604+ markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
2605+ soup = BeautifulSoup(markup, 'html.parser')
2606+ a_tag = soup.a
2607+ i_tag = soup.i
2608+
2609+ i_tag.decompose()
2610+ a_tag
2611+ # <a href="http://example.com/">I linked to</a>
2612+
2613+El comportamiento de una :py:class:`Tag` o :py:class:`NavigableString` descompuesta
2614+no está definido y no deberías usarlo para nada. Si no estás seguro si algo
2615+ha sido descompuesto, puedes comprobar su propiedad ``.decomposed``
2616+`(nuevo en Beautiful Soup 4.9.0)`::
2617+
2618+ i_tag.decomposed
2619+ # True
2620+
2621+ a_tag.decomposed
2622+ # False
2623+
2624+
2625+.. _replace_with():
2626+
2627+``replace_with()``
2628+==================
2629+
2630+``PageElement.replace_with()`` elimina una etiqueta o cadena del árbol,
2631+y lo reemplaza con una o más etiquetas de tu elección::
2632+
2633+ markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
2634+ soup = BeautifulSoup(markup, 'html.parser')
2635+ a_tag = soup.a
2636+
2637+ new_tag = soup.new_tag("b")
2638+ new_tag.string = "example.com"
2639+ a_tag.i.replace_with(new_tag)
2640+
2641+ a_tag
2642+ # <a href="http://example.com/">I linked to <b>example.com</b></a>
2643+
2644+ bold_tag = soup.new_tag("b")
2645+ bold_tag.string = "example"
2646+ i_tag = soup.new_tag("i")
2647+ i_tag.string = "net"
2648+ a_tag.b.replace_with(bold_tag, ".", i_tag)
2649+
2650+ a_tag
2651+ # <a href="http://example.com/">I linked to <b>example</b>.<i>net</i></a>
2652+
2653+
2654+``replace_with()`` devuelve la etiqueta o cadena que se reemplazó,
2655+así que puedes examinarla o añadirla de nuevo a otra parte del árbol.
2656+
2657+`La capacidad de pasar múltiples argumentos a replace_with() es nueva
2658+en Beautiful Soup 4.10.0.`
2659+
2660+
2661+``wrap()``
2662+==========
2663+
2664+``PageElement.wrap()`` envuelve un elemento en la etiqueta que especificas.
2665+Devuelve la nueva envoltura::
2666+
2667+ soup = BeautifulSoup("<p>I wish I was bold.</p>", 'html.parser')
2668+ soup.p.string.wrap(soup.new_tag("b"))
2669+ # <b>I wish I was bold.</b>
2670+
2671+ soup.p.wrap(soup.new_tag("div"))
2672+ # <div><p><b>I wish I was bold.</b></p></div>
2673+
2674+`Este método es nuevo en Beautiful Soup 4.0.5.`
2675+
2676+``unwrap()``
2677+============
2678+
2679+``Tag.unwrap()`` es el opuesto de ``wrap()``. Reemplaza una
2680+etiqueta con lo que haya dentro de lo que haya en esa etiqueta.
2681+Es bueno para eliminar anotaciones::
2682+
2683+ markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
2684+ soup = BeautifulSoup(markup, 'html.parser')
2685+ a_tag = soup.a
2686+
2687+ a_tag.i.unwrap()
2688+ a_tag
2689+ # <a href="http://example.com/">I linked to example.com</a>
2690+
2691+Como ``replace_with()``, ``unwrap()`` devuelve la etiqueta que fue
2692+reemplazada.
2693+
2694+``smooth()``
2695+============
2696+
2697+Tras llamar a un puñado de métodos que modifican el árbol analizado, puedes
2698+acabar con dos o más objetos :py:class:`NavigableString` uno al lado del otro.
2699+Beautiful Soup no tiene ningún problema con esto, pero como no puede ocurrir
2700+en un documento recién analizado, puedes no esperar un comportamiento como
2701+el siguiente::
2702+
2703+ soup = BeautifulSoup("<p>A one</p>", 'html.parser')
2704+ soup.p.append(", a two")
2705+
2706+ soup.p.contents
2707+ # ['A one', ', a two']
2708+
2709+ print(soup.p.encode())
2710+ # b'<p>A one, a two</p>'
2711+
2712+ print(soup.p.prettify())
2713+ # <p>
2714+ # A one
2715+ # , a two
2716+ # </p>
2717+
2718+Puedes llamar a ``Tag.smooth()`` para limpiar el árbol analizado consolidando
2719+cadenas adyacentes::
2720+
2721+ soup.smooth()
2722+
2723+ soup.p.contents
2724+ # ['A one, a two']
2725+
2726+ print(soup.p.prettify())
2727+ # <p>
2728+ # A one, a two
2729+ # </p>
2730+
2731+`Este método es nuevo en Beautiful Soup 4.8.0.`
2732+
2733+========
2734+ Salida
2735+========
2736+
2737+.. _.prettyprinting:
2738+
2739+*Pretty-printing*
2740+=================
2741+
2742+El método ``prettify()`` convertirá un árbol analizado de Beautiful Soup
2743+en una cadena de caracteres Unicode bien formateado, con una línea
2744+para cada etiqueta y cada cadena::
2745+
2746+ markup = '<html><head><body><a href="http://example.com/">I linked to <i>example.com</i></a>'
2747+ soup = BeautifulSoup(markup, 'html.parser')
2748+ soup.prettify()
2749+ # '<html>\n <head>\n </head>\n <body>\n <a href="http://example.com/">\n...'
2750+
2751+ print(soup.prettify())
2752+ # <html>
2753+ # <head>
2754+ # </head>
2755+ # <body>
2756+ # <a href="http://example.com/">
2757+ # I linked to
2758+ # <i>
2759+ # example.com
2760+ # </i>
2761+ # </a>
2762+ # </body>
2763+ # </html>
2764+
2765+Puedes llamar ``prettify()`` a alto nivel sobre el objeto :py:class:`BeautifulSoup`,
2766+o sobre cualquiera de sus objetos :py:class:`Tag`::
2767+
2768+ print(soup.a.prettify())
2769+ # <a href="http://example.com/">
2770+ # I linked to
2771+ # <i>
2772+ # example.com
2773+ # </i>
2774+ # </a>
2775+
2776+Como añade un espacio en blanco (en la forma de saltos de líneas),
2777+``prettify()`` cambia el sentido del documento HTML y no debe ser
2778+usado para reformatearlo. El objetivo de ``prettify()`` es ayudarte
2779+a entender visualmente la estructura del documento en el que trabajas.
2780+
2781+*Non-pretty printing*
2782+=====================
2783+
2784+Si tan solo quieres una cadena, sin ningún formateo adornado,
2785+puedes llamar a ``str()`` en un objeto :py:class:`BeautifulSoup`, o
2786+sobre una :py:class:`Tag` dentro de él::
2787+
2788+ str(soup)
2789+ # '<html><head></head><body><a href="http://example.com/">I linked to <i>example.com</i></a></body></html>'
2790+
2791+ str(soup.a)
2792+ # '<a href="http://example.com/">I linked to <i>example.com</i></a>'
2793+
2794+La función ``str()`` devuelve una cadena codificada en UTF-8. Mira
2795+`Codificaciones`_ para otras opciones.
2796+
2797+Puedes también llamar a ``encode()`` para obtener un bytestring, y
2798+``decode()`` para obtener Unicode.
2799+
2800+.. _output_formatters:
2801+
2802+Formatos de salida
2803+==================
2804+
2805+Si le das a Beautiful Soup un documento que contenga entidades HTML
2806+como "&lquot;", serán convertidas a caracteres Unicode::
2807+
2808+ soup = BeautifulSoup("&ldquo;Dammit!&rdquo; he said.", 'html.parser')
2809+ str(soup)
2810+ # '“Dammit!” he said.'
2811+
2812+Si después conviertes el documento a bytestring, los caracteres Unicode
2813+serán convertidos a UTF-8. No obtendrás de nuevo las entidades HTML::
2814+
2815+ soup.encode("utf8")
2816+ # b'\xe2\x80\x9cDammit!\xe2\x80\x9d he said.'
2817+
2818+Por defecto, los únicos caracteres que se formatean en la salida son
2819+ampersands y comillas anguladas simples. Estas se transforman en
2820+"&amp;", "&lt;" y "&gt;", así Beautiful Soup no genera inadvertidamente
2821+HTML o XML inválido::
2822+
2823+ soup = BeautifulSoup("<p>The law firm of Dewey, Cheatem, & Howe</p>", 'html.parser')
2824+ soup.p
2825+ # <p>The law firm of Dewey, Cheatem, &amp; Howe</p>
2826+
2827+ soup = BeautifulSoup('<a href="http://example.com/?foo=val1&bar=val2">A link</a>', 'html.parser')
2828+ soup.a
2829+ # <a href="http://example.com/?foo=val1&amp;bar=val2">A link</a>
2830+
2831+Puedes cambiar este comportamiento dando un valor al argumento
2832+``formatter`` de ``prettify()``, ``encode()`` o ``decode()``.
2833+Beautiful Soup reconoce cinco posibles valores para ``formatter``.
2834+
2835+El valor por defecto es ``formatter="minimal"``. Las cadenas solo
2836+serán procesadas lo suficiente como para asegurar que Beautiful Soup
2837+genera HTML/XML válido::
2838+
2839+ french = "<p>Il a dit &lt;&lt;Sacr&eacute; bleu!&gt;&gt;</p>"
2840+ soup = BeautifulSoup(french, 'html.parser')
2841+ print(soup.prettify(formatter="minimal"))
2842+ # <p>
2843+ # Il a dit &lt;&lt;Sacré bleu!&gt;&gt;
2844+ # </p>
2845+
2846+Si pasas ``formatter="html"``, Beautiful Soup convertirá caracteres
2847+Unicode a entidades HTML cuando sea posible::
2848+
2849+ print(soup.prettify(formatter="html"))
2850+ # <p>
2851+ # Il a dit &lt;&lt;Sacr&eacute; bleu!&gt;&gt;
2852+ # </p>
2853+
2854+Si pasas ``formatter="html5"``, es similar a
2855+``formatter="html"``, pero Beautiful Soup omitirá la barra de
2856+cierre en etiquetas HTML vacías como "br"::
2857+
2858+ br = BeautifulSoup("<br>", 'html.parser').br
2859+
2860+ print(br.encode(formatter="html"))
2861+ # b'<br/>'
2862+
2863+ print(br.encode(formatter="html5"))
2864+ # b'<br>'
2865+
2866+Además, cualquier atributo cuyos valores son la cadena de
2867+caracteres vacía se convertirán en atributos booleanos al
2868+estilo HTML::
2869+
2870+ option = BeautifulSoup('<option selected=""></option>').option
2871+ print(option.encode(formatter="html"))
2872+ # b'<option selected=""></option>'
2873+
2874+ print(option.encode(formatter="html5"))
2875+ # b'<option selected></option>'
2876+
2877+*(Este comportamiento es nuevo a partir de Beautiful Soup 4.10.0.)*
2878+
2879+Si pasas ``formatter=None``, Beautiful Soup no modificará en absoluto
2880+las cadenas a la salida. Esta es la opción más rápida, pero puede
2881+ocasionar que Beautiful Soup genere HTML/XML inválido, como en estos
2882+ejemplos::
2883+
2884+ print(soup.prettify(formatter=None))
2885+ # <p>
2886+ # Il a dit <<Sacré bleu!>>
2887+ # </p>
2888+
2889+ link_soup = BeautifulSoup('<a href="http://example.com/?foo=val1&bar=val2">A link</a>', 'html.parser')
2890+ print(link_soup.a.encode(formatter=None))
2891+ # b'<a href="http://example.com/?foo=val1&bar=val2">A link</a>'
2892+
2893+*Objetos Formatter*
2894+-------------------
2895+
2896+Si necesitas un control más sofisticado sobre tu salida, puedes
2897+instanciar uno de las clases *formatters* de Beautiful Soup y pasar
2898+dicho objeto a ``formatter``.
2899+
2900+.. py:class:: HTMLFormatter
2901+
2902+Usado para personalizar las reglas de formato para documentos HTML.
2903+
2904+Aquí está el *formatter* que convierte cadenas de caracteres a mayúsculas,
2905+como si están en un nodo de texto o en el valor de un atributo::
2906+
2907+ from bs4.formatter import HTMLFormatter
2908+ def uppercase(str):
2909+ return str.upper()
2910+
2911+ formatter = HTMLFormatter(uppercase)
2912+
2913+ print(soup.prettify(formatter=formatter))
2914+ # <p>
2915+ # IL A DIT <<SACRÉ BLEU!>>
2916+ # </p>
2917+
2918+ print(link_soup.a.prettify(formatter=formatter))
2919+ # <a href="HTTP://EXAMPLE.COM/?FOO=VAL1&BAR=VAL2">
2920+ # A LINK
2921+ # </a>
2922+
2923+Este es el *formatter* que incrementa la sangría cuando se realiza
2924+*pretty-printing*::
2925+
2926+ formatter = HTMLFormatter(indent=8)
2927+ print(link_soup.a.prettify(formatter=formatter))
2928+ # <a href="http://example.com/?foo=val1&bar=val2">
2929+ # A link
2930+ # </a>
2931+
2932+.. py:class:: XMLFormatter
2933+
2934+Usado para personalizar las reglas de formateo para documentos XML.
2935+
2936+Escribir tu propio *formatter*
2937+------------------------------
2938+
2939+Crear una subclase a partir de :py:class:`HTMLFormatter` p :py:class:`XMLFormatter`
2940+te dará incluso más control sobre la salida. Por ejemplo, Beautiful Soup
2941+ordena por defecto los atributos en cada etiqueta::
2942+
2943+ attr_soup = BeautifulSoup(b'<p z="1" m="2" a="3"></p>', 'html.parser')
2944+ print(attr_soup.p.encode())
2945+ # <p a="3" m="2" z="1"></p>
2946+
2947+Para detener esto, puedes modificar en la subclase creada
2948+el método ``Formatter.attributes()``, que controla los atributos
2949+que se ponen en la salida y en qué orden. Esta implementación también
2950+filtra el atributo llamado "m" cuando aparezca::
2951+
2952+ class UnsortedAttributes(HTMLFormatter):
2953+ def attributes(self, tag):
2954+ for k, v in tag.attrs.items():
2955+ if k == 'm':
2956+ continue
2957+ yield k, v
2958+
2959+ print(attr_soup.p.encode(formatter=UnsortedAttributes()))
2960+ # <p z="1" a="3"></p>
2961+
2962+Una última advertencia: si creas un objeto :py:class:`CData`, el texto
2963+dentro de ese objeto siempre se muestra `exactamente como aparece, sin
2964+ningún formato`. Beautiful Soup llamará a la función de sustitución de
2965+entidad, por si hubieses escrito una función a medida que cuenta
2966+todas las cadenas en el documento o algo así, pero ignorará el
2967+valor de retorno::
2968+
2969+ from bs4.element import CData
2970+ soup = BeautifulSoup("<a></a>", 'html.parser')
2971+ soup.a.string = CData("one < three")
2972+ print(soup.a.prettify(formatter="html"))
2973+ # <a>
2974+ # <![CDATA[one < three]]>
2975+ # </a>
2976+
2977+
2978+``get_text()``
2979+==============
2980+
2981+Si solo necesitas el texto legible dentro de un documento o etiqueta, puedes
2982+usar el método ``get_text()``. Devuelve todo el texto dentro del documento o
2983+dentro de la etiqueta, como una sola cadena caracteres Unicode::
2984+
2985+ markup = '<a href="http://example.com/">\nI linked to <i>example.com</i>\n</a>'
2986+ soup = BeautifulSoup(markup, 'html.parser')
2987+
2988+ soup.get_text()
2989+ '\nI linked to example.com\n'
2990+ soup.i.get_text()
2991+ 'example.com'
2992+
2993+Puedes especificar una cadena que usará para unir los trozos
2994+de texto::
2995+
2996+ # soup.get_text("|")
2997+ '\nI linked to |example.com|\n'
2998+
2999+Puedes indicar a Beautiful Soup que quite los espacios en blanco del
3000+comienzo y el final de cada trozo de texto::
3001+
3002+ # soup.get_text("|", strip=True)
3003+ 'I linked to|example.com'
3004+
3005+Pero en ese punto puedas querer usar mejor el generador
3006+:ref:`.stripped_strings <string-generators>`, y procesar el texto
3007+por tu cuenta::
3008+
3009+ [text for text in soup.stripped_strings]
3010+ # ['I linked to', 'example.com']
3011+
3012+*A partir de Beautiful Soup version 4.9.0, cuando lxml o html.parser
3013+se usan, el contenido de las etiquetas <script>, <style>, y <template>
3014+no se consideran texto, ya que esas etiquetas no son parte de la parte
3015+legible del contenido de la página.*
3016+
3017+*A partir de de Beautiful Soup version 4.10.0, puedes llamar a get_text(),
3018+.strings, o .stripped_strings en un objeto NavigableString. Devolverá
3019+el propio objeto, o nada, así que la única razón para hacerlo es cuando
3020+estás iterando sobre una lista mixta.*
3021+
3022+==================================
3023+ Especificar el analizador a usar
3024+==================================
3025+
3026+Si lo único que necesitas es analizar algún HTML, puedes ponerlo en
3027+el constructor de :py:class:`BeautifulSoup`, y probablemente irá bien.
3028+Beautiful Soup elegirá un analizador por ti y analizará los datos.
3029+Pero hay algunos argumentos adicionales que puedes pasar al constructor
3030+para cambiar el analizador que se usa.
3031+
3032+El primer argumento del constructor de :py:class:`BeautifulSoup` es una cadena
3033+o un gestor de archivos abierto--el marcado que quieres analizar. El segundo
3034+argumento es `cómo` quieres que el marcado analizado.
3035+
3036+Si no especificas nada, obtendrás el mejor analizador HTML que tengas
3037+instalado. Beautiful Soup clasifica al analizador de lxml como el mejor,
3038+después el de html5lib, y luego el analizador integrado en Python. Puedes
3039+sobrescribir esto especificando uno de los siguientes:
3040+
3041+* El tipo de marcado que quieres analizar. Actualmente se soportan
3042+ "html", "xml", y "html5".
3043+
3044+* El nombre de la librería del analizador que quieras usar. Actualmente se
3045+ soportan "lxml", "html5lib", y "html.parser" (el analizador HTML integrado
3046+ de Python).
3047+
3048+La sección `Instalar un analizador`_ contraste los analizadores admitidos.
3049+
3050+Si no tienes un analizador apropiado instalado, Beautiful Soup ignorará
3051+tu petición y elegirá un analizador diferente. Ahora mismo, el único
3052+analizador XML es lxml. Si no tienes lxml instalado, solicitar un
3053+analizador XML no te dará uno, y pedir por "lxml" tampoco funcionará.
3054+
3055+Diferencias entre analizadores
3056+==============================
3057+
3058+Beautiful Soup presenta la misma interfaz que varios analizadores,
3059+pero cada uno es diferente. Analizadores diferentes crearán
3060+árboles analizados diferentes a partir del mismo documento. La mayores
3061+diferencias están entre los analizadores HTML y los XML. Este es un
3062+documento corto, analizado como HTML usando el analizador que viene
3063+con Python::
3064+
3065+ BeautifulSoup("<a><b/></a>", "html.parser")
3066+ # <a><b></b></a>
3067+
3068+Como una sola etiqueta <b/> no es HTML válido, html.parser lo convierte a
3069+un par <b><b/>.
3070+
3071+Aquí está el mismo documento analizado como XML (correr esto requiere que
3072+tengas instalado lxml). Debe notarse que la etiqueta independiente
3073+<b/> se deja sola, y que en el documento se incluye una declaración XML
3074+en lugar de introducirlo en una etiqueta <html>::
3075+
3076+ print(BeautifulSoup("<a><b/></a>", "xml"))
3077+ # <?xml version="1.0" encoding="utf-8"?>
3078+ # <a><b/></a>
3079+
3080+Hay también diferencias entre analizadores HTML. Si le das a Beautiful
3081+Soup un documento HTML perfectamente formado, esas diferencias no
3082+importan. Un analizador será más rápido que otro, pero todos te darán
3083+una estructura de datos que será exactamente como el documento HTML
3084+original.
3085+
3086+Pero si el documento no está perfectamente formado, analizadores
3087+diferentes darán diferentes resultados. A continuación se presenta
3088+un documento corto e incorrecto analizado usando el analizador
3089+HTML de lxml. Debe considerarse que la etiqueta <a> es envuelta
3090+en las etiquetas <body> y <html>, y que la etiqueta colgada </p>
3091+simplemente se ignora::
3092+
3093+ BeautifulSoup("<a></p>", "lxml")
3094+ # <html><body><a></a></body></html>
3095+
3096+Este es el mismo documento analizado usando html5lib::
3097+
3098+ BeautifulSoup("<a></p>", "html5lib")
3099+ # <html><head></head><body><a><p></p></a></body></html>
3100+
3101+En lugar de ignorar la etiqueta colgada </p>, html5lib la empareja
3102+con una etiqueta inicial <p>. html5lib también añade una etiqueta <head>
3103+vacía; lxml no se molesta.
3104+
3105+Este es el mismo documento analizado usando el analizador HTML integrado
3106+en Python::
3107+
3108+ BeautifulSoup("<a></p>", "html.parser")
3109+ # <a></a>
3110+
3111+Como lxml, este analizador ignora la etiqueta clausura </p>.
3112+A diferencia de html5lib o lxml, este analizador no intenta
3113+crear un documento HTML bien formado añadiendo las etiquetas
3114+<html> o <body>.
3115+
3116+Como el documento "<a></p>" es inválido, ninguna de estas técnicas
3117+es la forma 'correcta' de gestionarlo. El analizador de html5lib usa
3118+técnicas que son parte del estándar de HTML5, así que es la que más
3119+se puede aproximar a ser la manera correcta, pero las tres técnicas
3120+son legítimas.
3121+
3122+Las diferencias entre analizadores pueden afectar a tu script. Si
3123+estás planeando en distribuir tu script con otras personas, o
3124+ejecutarlo en varias máquinas, deberías especificar un analizador
3125+en el constructor de :py:class:`BeautifulSoup`. Eso reducirá
3126+las probabilidad que tus usuarios analicen un documento diferentemente
3127+de la manera en la que tú lo analizas.
3128+
3129+================
3130+ Codificaciones
3131+================
3132+
3133+Cualquier documento HTML o XML está escrito en una codificación
3134+específica como ASCII o UTF-8. Pero cuando cargas ese documento en
3135+Beautiful Soup, descubrirás que se convierte en Unicode::
3136+
3137+ markup = "<h1>Sacr\xc3\xa9 bleu!</h1>"
3138+ soup = BeautifulSoup(markup, 'html.parser')
3139+ soup.h1
3140+ # <h1>Sacré bleu!</h1>
3141+ soup.h1.string
3142+ # 'Sacr\xe9 bleu!'
3143+
3144+No es magia (seguro que eso sería genial). Beautiful Soup usa una
3145+sub-librería llamada `Unicode, Dammit`_ para detectar la codificación
3146+de un documento y convertirlo a Unicode. La codificación auto detectada
3147+está disponible con el atributo ``.original_encoding`` del objeto
3148+:py:class:`Beautiful Soup`::
3149+
3150+ soup.original_encoding
3151+ 'utf-8'
3152+
3153+Unicode, Dammit estima correctamente la mayor parte del tiempo, pero
3154+a veces se equivoca. A veces estima correctamente, pero solo después
3155+de una búsqueda byte a byte del documento que tarda mucho tiempo.
3156+Si ocurre que sabes a priori la codificación del documento, puedes
3157+evitar errores y retrasos pasándola al constructor de :py:class:`BeautifulSoup`
3158+con ``from_encoding``.
3159+
3160+Este es un documento escrito es ISO-8859-8. El documento es tan corto que
3161+Unicode, Dammit no da en el clave, y lo identifica erróneamente como
3162+ISO-8859-7::
3163+
3164+ markup = b"<h1>\xed\xe5\xec\xf9</h1>"
3165+ soup = BeautifulSoup(markup, 'html.parser')
3166+ print(soup.h1)
3167+ # <h1>νεμω</h1>
3168+ print(soup.original_encoding)
3169+ # iso-8859-7
3170+
3171+Podemos arreglarlo pasándole el correcto a ``from_encoding``::
3172+
3173+ soup = BeautifulSoup(markup, 'html.parser', from_encoding="iso-8859-8")
3174+ print(soup.h1)
3175+ # <h1>םולש</h1>
3176+ print(soup.original_encoding)
3177+ # iso8859-8
3178+
3179+Si no sabes cuál es la codificación correcta, pero sabes que Unicode, Dammit
3180+está suponiendo mal, puedes pasarle las opciones mal estimadas con
3181+``exclude_encodings``::
3182+
3183+ soup = BeautifulSoup(markup, 'html.parser', exclude_encodings=["iso-8859-7"])
3184+ print(soup.h1)
3185+ # <h1>םולש</h1>
3186+ print(soup.original_encoding)
3187+ # WINDOWS-1255
3188+
3189+Windows-1255 no es correcto al 100%, pero esa codificación es
3190+una superconjunto compatible con ISO-8859-8, así que se acerca
3191+lo suficiente. (``exlcude_encodings`` es una nueva característica
3192+en Beautiful Soup 4.4.0).
3193+
3194+En casos raros (normalmente cuando un documento UTF-8 contiene texto
3195+escrito en una codificación completamente diferente), la única manera
3196+para obtener Unicode es reemplazar algunos caracteres con el carácter
3197+Unicode especial "REPLACEMENT CHARACTER" (U+FFFD, �). Si Unicode, Dammit
3198+necesita hacer esto, establecerá el atributo ``.contains_replacement_characters``
3199+a ``True`` en el objeto ``UnicodeDammit`` o :py:class:`BeautifulSoup`. Esto
3200+te permite saber si la representación Unicode no es una representación
3201+exacta de la original--algún dato se ha perdido. Si un documento contiene �,
3202+pero ``contains_replacement_characteres`` es ``False``, sabrás que �
3203+estaba allí originalmente (como lo está en este párrafo) y no implica
3204+datos perdidos.
3205+
3206+Codificación de salida
3207+======================
3208+
3209+Cuando escribas completamente un documento desde Beautiful Soup,
3210+obtienes un documento UTF-8, incluso cuando el documento no está en UTF-8
3211+por el que empezar. Este es un documento escrito con la codificación Latin-1::
3212+
3213+ markup = b'''
3214+ <html>
3215+ <head>
3216+ <meta content="text/html; charset=ISO-Latin-1" http-equiv="Content-type" />
3217+ </head>
3218+ <body>
3219+ <p>Sacr\xe9 bleu!</p>
3220+ </body>
3221+ </html>
3222+ '''
3223+
3224+ soup = BeautifulSoup(markup, 'html.parser')
3225+ print(soup.prettify())
3226+ # <html>
3227+ # <head>
3228+ # <meta content="text/html; charset=utf-8" http-equiv="Content-type" />
3229+ # </head>
3230+ # <body>
3231+ # <p>
3232+ # Sacré bleu!
3233+ # </p>
3234+ # </body>
3235+ # </html>
3236+
3237+Fíjate bien que la etiqueta <meta> ha sido reescrita para reflejar el hecho
3238+de que el documento está ahora en UTF-8.
3239+
3240+Si no quieres UTF-8, puedes pasar una codificación a ``prettify()``::
3241+
3242+ print(soup.prettify("latin-1"))
3243+ # <html>
3244+ # <head>
3245+ # <meta content="text/html; charset=latin-1" http-equiv="Content-type" />
3246+ # ...
3247+
3248+También puedes llamar a encode() sobre el objeto :py:class:`BeautifulSoup`, o
3249+cualquier elemento en el objeto, como si fuese una cadena de Python::
3250+
3251+ soup.p.encode("latin-1")
3252+ # b'<p>Sacr\xe9 bleu!</p>'
3253+
3254+ soup.p.encode("utf-8")
3255+ # b'<p>Sacr\xc3\xa9 bleu!</p>'
3256+
3257+Cualesquiera caracteres que no puedan ser representados en la codificación
3258+que has elegido se convierten en referencias a entidades numéricas XML.
3259+Este es un documento que incluye el carácter Unicode SNOWMAN::
3260+
3261+ markup = u"<b>\N{SNOWMAN}</b>"
3262+ snowman_soup = BeautifulSoup(markup, 'html.parser')
3263+ tag = snowman_soup.b
3264+
3265+El carácter SNOWMAN puede ser parte de un documento UTF-8 (se parece a ☃),
3266+pero no hay representación para ese carácter en ISO-Latin-1 o ASCII,
3267+así que se convierte en "&#9731" para esas codificaciones::
3268+
3269+ print(tag.encode("utf-8"))
3270+ # b'<b>\xe2\x98\x83</b>'
3271+
3272+ print(tag.encode("latin-1"))
3273+ # b'<b>&#9731;</b>'
3274+
3275+ print(tag.encode("ascii"))
3276+ # b'<b>&#9731;</b>'
3277+
3278+Unicode, Dammit
3279+===============
3280+
3281+Puedes usar Unicode, Dammit sin usar Beautiful Soup. Es útil cuando
3282+tienes datos en una codificación desconocida y solo quieres convertirlo
3283+a Unicode::
3284+
3285+ from bs4 import UnicodeDammit
3286+ dammit = UnicodeDammit(b"\xc2\xabSacr\xc3\xa9 bleu!\xc2\xbb")
3287+ print(dammit.unicode_markup)
3288+ # «Sacré bleu!»
3289+ dammit.original_encoding
3290+ # 'utf-8'
3291+
3292+Los estimaciones de Unicode, Dammit será mucho más precisas si instalas
3293+una de estas librerías de Python: ``charset-normalizer``, ``chardet``,
3294+o ``cchardet``. Cuanto más datos le des a Unicode, Dammit, con mayor exactitud
3295+estimará. Si tienes alguna sospecha sobre las codificaciones que podrían ser, puedes
3296+pasárselas en una lista::
3297+
3298+ dammit = UnicodeDammit("Sacr\xe9 bleu!", ["latin-1", "iso-8859-1"])
3299+ print(dammit.unicode_markup)
3300+ # Sacré bleu!
3301+ dammit.original_encoding
3302+ # 'latin-1'
3303+
3304+Unicode, Dammit tiene dos características especiales que Beautiful Soup no usa.
3305+
3306+Comillas inteligentes
3307+---------------------
3308+
3309+Puedes usar Unicode, Dammit para convertir las comillas inteligentes de Microsoft
3310+a entidades HTML o XML::
3311+
3312+ markup = b"<p>I just \x93love\x94 Microsoft Word\x92s smart quotes</p>"
3313+
3314+ UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="html").unicode_markup
3315+ # '<p>I just &ldquo;love&rdquo; Microsoft Word&rsquo;s smart quotes</p>'
3316+
3317+ UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="xml").unicode_markup
3318+ # '<p>I just &#x201C;love&#x201D; Microsoft Word&#x2019;s smart quotes</p>'
3319+
3320+Puedes también convertir las comillas inteligentes de Microsoft a comillas ASCII::
3321+
3322+ UnicodeDammit(markup, ["windows-1252"], smart_quotes_to="ascii").unicode_markup
3323+ # '<p>I just "love" Microsoft Word\'s smart quotes</p>'
3324+
3325+Con suerte encontrarás esta característica útil, pero Beautiful Soup no la usa.
3326+Beautiful Soup prefiere el comportamiento por defecto, el cual es convertir
3327+las comillas inteligentes de Microsoft a caracteres Unicode junto al resto
3328+de cosas::
3329+
3330+ UnicodeDammit(markup, ["windows-1252"]).unicode_markup
3331+ # '<p>I just “love” Microsoft Word’s smart quotes</p>'
3332+
3333+Codificaciones inconsistentes
3334+-----------------------------
3335+
3336+A veces un documento está mayoritariamente en UTF-8, pero contiene
3337+caracteres Windows-1252 como (de nuevo) comillas inteligentes de Microsoft.
3338+Esto puede ocurrir cuando un sitio web incluye datos de múltiples fuentes.
3339+Puedes usar ``UnicodeDammit.detwingle()`` para convertir dicho documento en
3340+puro UTF-8. Este un ejemplo sencillo::
3341+
3342+ snowmen = (u"\N{SNOWMAN}" * 3)
3343+ quote = (u"\N{LEFT DOUBLE QUOTATION MARK}I like snowmen!\N{RIGHT DOUBLE QUOTATION MARK}")
3344+ doc = snowmen.encode("utf8") + quote.encode("windows_1252")
3345+
3346+Este documento es un desastre. Los muñecos de nieve están en UTF-8 y las
3347+comillas están en Windows-1252. Puedes mostrar los muñecos de nieve o
3348+las comillas, pero no ambos::
3349+
3350+ print(doc)
3351+ # ☃☃☃�I like snowmen!�
3352+
3353+ print(doc.decode("windows-1252"))
3354+ # ☃☃☃“I like snowmen!”
3355+
3356+Decodificar el documento en UTF-8 provoca un ``UnicodeDecodeError``, y
3357+decodificarlo como Windows-1252 te da un galimatías. Afortunadamente,
3358+``UnicodeDammit.detwingle()`` convertirá la cadena en puro UTF-8,
3359+permitiéndote decodificarlo en Unicode y mostrar el muñeco de nieve
3360+y marcas de comillas simultáneamente::
3361+
3362+ new_doc = UnicodeDammit.detwingle(doc)
3363+ print(new_doc.decode("utf8"))
3364+ # ☃☃☃“I like snowmen!”
3365+
3366+``UnicodeDammit.detwingle()`` solo sabe cómo gestionar Windows-1252 embebido
3367+en UTF-8 (o viceversa, supongo), pero este es el caso más común.
3368+
3369+Fíjate que debes saber que debes llamar a ``UnicodeDammit.detwingle()``
3370+en tus datos antes de pasarlo a :py:class:`BeautifulSoup` o el constructor
3371+de ``UnicodeDammit``. Beautiful Soup asume que un documento tiene una
3372+sola codificación, la que sea. Si quieres pasar un documento que contiene
3373+ambas UTF-8 y Windows-1252, es probable que piense que todo el documento
3374+es Windows-1252, y el documento se parecerá a ```☃☃☃“I like snowmen!”``.
3375+
3376+``UnicodeDammit.detwingle()`` es nuevo en Beautiful Soup 4.1.0.
3377+
3378+==================
3379+ Números de línea
3380+==================
3381+
3382+Los analizadores de ``html.parser`` y ``html5lib`` pueden llevar la cuenta
3383+de los lugares en el documento original donde se han encontrado cada etiqueta.
3384+Puedes acceder a esta información con ``Tag.sourceline`` (número de línea) y
3385+``Tag.sourcepos`` (posición del comienzo de una etiqueta en una línea)::
3386+
3387+ markup = "<p\n>Paragraph 1</p>\n <p>Paragraph 2</p>"
3388+ soup = BeautifulSoup(markup, 'html.parser')
3389+ for tag in soup.find_all('p'):
3390+ print(repr((tag.sourceline, tag.sourcepos, tag.string)))
3391+ # (1, 0, 'Paragraph 1')
3392+ # (3, 4, 'Paragraph 2')
3393+
3394+Debe destacarse que los dos analizadores entienden cosas ligeramente
3395+diferentes por ``sourceline`` y ``sourcepos``. Para html.parser, estos
3396+números representan la posición del signo "menor" inicial. Para html5lib,
3397+estos números representan la posición del signo "mayor" final::
3398+
3399+ soup = BeautifulSoup(markup, 'html5lib')
3400+ for tag in soup.find_all('p'):
3401+ print(repr((tag.sourceline, tag.sourcepos, tag.string)))
3402+ # (2, 0, 'Paragraph 1')
3403+ # (3, 6, 'Paragraph 2')
3404+
3405+Puedes interrumpir esta característica pasado ``store_line_numbers=False``
3406+en el constructor de :py:class:`BeautifulSoup`::
3407+
3408+ markup = "<p\n>Paragraph 1</p>\n <p>Paragraph 2</p>"
3409+ soup = BeautifulSoup(markup, 'html.parser', store_line_numbers=False)
3410+ print(soup.p.sourceline)
3411+ # None
3412+
3413+`Esta característica es nueva en 4.8.1, y los analizadores basados en lxml no la
3414+soportan.`
3415+
3416+===============================
3417+ Comparar objetos por igualdad
3418+===============================
3419+
3420+Beautiful Soup indica que dos objetos :py:class:`NavigableString` o :py:class:`Tag`
3421+son iguales cuando representan al mismo marcado HTML o XML. En este ejemplo,
3422+las dos etiquetas <b> son tratadas como iguales, aunque están en diferentes
3423+partes del objeto árbol, porque ambas son "<b>pizza</b>"::
3424+
3425+ markup = "<p>I want <b>pizza</b> and more <b>pizza</b>!</p>"
3426+ soup = BeautifulSoup(markup, 'html.parser')
3427+ first_b, second_b = soup.find_all('b')
3428+ print(first_b == second_b)
3429+ # True
3430+
3431+ print(first_b.previous_element == second_b.previous_element)
3432+ # False
3433+
3434+Si quieres saber si dos variables se refieren a exactamente el mismo
3435+objeto, usa `is`::
3436+
3437+ print(first_b is second_b)
3438+ # False
3439+
3440+==================================
3441+ Copiar objetos de Beautiful Soup
3442+==================================
3443+
3444+Puedes usar ``copy.copy()`` para crear una copia de cualquier
3445+:py:class:`Tag` o :py:class:`NavigableString`::
3446+
3447+ import copy
3448+ p_copy = copy.copy(soup.p)
3449+ print(p_copy)
3450+ # <p>I want <b>pizza</b> and more <b>pizza</b>!</p>
3451+
3452+La copia se considera igual que la original, ya que representa el mismo
3453+marcado que el original, pero no son el mismo objeto::
3454+
3455+ print(soup.p == p_copy)
3456+ # True
3457+
3458+ print(soup.p is p_copy)
3459+ # False
3460+
3461+La única diferencia real es que la copia está completamente desconectada
3462+del objeto árbol de Beautiful Soup, como si ``extract()`` hubiese sido
3463+llamada sobre ella::
3464+
3465+ print(p_copy.parent)
3466+ # None
3467+
3468+Esto es porque dos diferentes objetos :py:class:`Tag` no pueden ocupar
3469+el mismo espacio al mismo tiempo.
3470+
3471+=========================================
3472+ Personalización avanzada del analizador
3473+=========================================
3474+
3475+Beautiful Soup ofrece numerosas vías para personalizar la manera en la que
3476+el analizador trata HTML o XML entrante. Esta sección cubre las técnicas
3477+de personalizadas usadas más comúnmente.
3478+
3479+Analizar solo parte del documento
3480+=================================
3481+
3482+Digamos que quieres usar Beautiful Soup para observar las etiquetas <a> de un
3483+documento. Es un malgasto de tiempo y memoria analizar todo el documento y
3484+después recorrerlo una y otra vez buscando etiquetas <a>. Sería mucho más
3485+rápido ignorar todo lo que no sea una etiqueta <a> desde el principio.
3486+La clase :py:class:`SoupStrainer` te permite elegir qué partes de un
3487+documento entrante se analizan. Tan solo crea un :py:class:`SoupStrainer` y
3488+pásalo al constructor de :py:class:`BeautifulSoup` en el argumento ``parse_only``.
3489+
3490+(Debe notarse que *esta característica no funcionará si estás usando el
3491+analizador de html5lib*. Si usas html5lib, todo el documento será analizado,
3492+no importa el resto. Esto es porque html5lib constantemente reorganiza el
3493+árbol analizado conforme trabaja, y si alguna parte del documento no
3494+consigue introducirse en el árbol analizado, se quedará colgado. Para evitar
3495+confusión en los ejemplos más abajo forzaré a Beautiful Soup a que use
3496+el analizador integrado de Python).
3497+
3498+.. py:class:: SoupStrainer
3499+
3500+La clase :py:class:`SoupStrainer` toma los mismos argumentos que un típico
3501+método de `Buscar en el árbol`_: :ref:`name <name>`, :ref:`attrs <attrs>`,
3502+:ref:`string <string>`, y :ref:`**kwargs <kwargs>`. Estos son tres objetos
3503+:py:class:`SoupStrainer`::
3504+
3505+ from bs4 import SoupStrainer
3506+
3507+ only_a_tags = SoupStrainer("a")
3508+
3509+ only_tags_with_id_link2 = SoupStrainer(id="link2")
3510+
3511+ def is_short_string(string):
3512+ return string is not None and len(string) < 10
3513+
3514+ only_short_strings = SoupStrainer(string=is_short_string)
3515+
3516+Voy a traer de nuevo el documento de "Las tres hermanas" una vez más,
3517+y veremos cómo parece el documento cuando es analizado con estos
3518+tres objetos :py:class:`SoupStrainer`::
3519+
3520+ html_doc = """<html><head><title>The Dormouse's story</title></head>
3521+ <body>
3522+ <p class="title"><b>The Dormouse's story</b></p>
3523+
3524+ <p class="story">Once upon a time there were three little sisters; and their names were
3525+ <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
3526+ <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
3527+ <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
3528+ and they lived at the bottom of a well.</p>
3529+
3530+ <p class="story">...</p>
3531+ """
3532+
3533+ print(BeautifulSoup(html_doc, "html.parser", parse_only=only_a_tags).prettify())
3534+ # <a class="sister" href="http://example.com/elsie" id="link1">
3535+ # Elsie
3536+ # </a>
3537+ # <a class="sister" href="http://example.com/lacie" id="link2">
3538+ # Lacie
3539+ # </a>
3540+ # <a class="sister" href="http://example.com/tillie" id="link3">
3541+ # Tillie
3542+ # </a>
3543+
3544+ print(BeautifulSoup(html_doc, "html.parser", parse_only=only_tags_with_id_link2).prettify())
3545+ # <a class="sister" href="http://example.com/lacie" id="link2">
3546+ # Lacie
3547+ # </a>
3548+
3549+ print(BeautifulSoup(html_doc, "html.parser", parse_only=only_short_strings).prettify())
3550+ # Elsie
3551+ # ,
3552+ # Lacie
3553+ # and
3554+ # Tillie
3555+ # ...
3556+ #
3557+
3558+Puedes también pasar un :py:class:`SoupStrainer` en cualquiera de los métodos
3559+cubiertos en `Buscar en el árbol`_. Esto probablemente no sea terriblemente útil,
3560+pero pensé en mencionarlo::
3561+
3562+ soup = BeautifulSoup(html_doc, 'html.parser')
3563+ soup.find_all(only_short_strings)
3564+ # ['\n\n', '\n\n', 'Elsie', ',\n', 'Lacie', ' and\n', 'Tillie',
3565+ # '\n\n', '...', '\n']
3566+
3567+Personalizar atributos multivaluados
3568+====================================
3569+
3570+En un documento HTML, a un atributo como ``class`` se le da una lista
3571+de valores, y a un atributo como ``id`` se le da un solo valor, porque
3572+la especificación de HTML trata a esos atributos de manera diferente::
3573+
3574+ markup = '<a class="cls1 cls2" id="id1 id2">'
3575+ soup = BeautifulSoup(markup, 'html.parser')
3576+ soup.a['class']
3577+ # ['cls1', 'cls2']
3578+ soup.a['id']
3579+ # 'id1 id2'
3580+
3581+Puedes interrumpir esto pasando ``multi_values_attributes=None``. Entonces
3582+a todos los atributos se les dará un solo valor::
3583+
3584+ soup = BeautifulSoup(markup, 'html.parser', multi_valued_attributes=None)
3585+ soup.a['class']
3586+ # 'cls1 cls2'
3587+ soup.a['id']
3588+ # 'id1 id2'
3589+
3590+Puedes personalizar este comportamiento un poco pasando un diccionario
3591+a ``multi_values_attributes``. Si lo necesitas, échale un vistazo a
3592+``HTMLTreeBuilder.DEFAULT_CDATA_LIST_ATTRIBUTES`` para ver la configuración
3593+que Beautiful Soup usa por defecto, que está basada en la especificación
3594+HTML.
3595+
3596+`(Esto es una nueva característica en Beautiful Soup 4.8.0).`
3597+
3598+Gestionar atributos duplicados
3599+==============================
3600+
3601+Cuando se use el analizador de ``html.parser``, puedes usar
3602+el argumento del constructor ``on_duplicate_attribute`` para personalizar
3603+qué hace Beautiful Soup cuando encuentra una etiqueta que define el mismo
3604+atributo más de una vez::
3605+
3606+ markup = '<a href="http://url1/" href="http://url2/">'
3607+
3608+El comportamiento por defecto es usar el último valor encontrado en la
3609+etiqueta::
3610+
3611+ soup = BeautifulSoup(markup, 'html.parser')
3612+ soup.a['href']
3613+ # http://url2/
3614+
3615+ soup = BeautifulSoup(markup, 'html.parser', on_duplicate_attribute='replace')
3616+ soup.a['href']
3617+ # http://url2/
3618+
3619+Con ``on_duplicate_attribute='ignore'`` puedes indicar a Beautiful Soup que
3620+use el `primer` valor encontrado e ignorar el resto::
3621+
3622+ soup = BeautifulSoup(markup, 'html.parser', on_duplicate_attribute='ignore')
3623+ soup.a['href']
3624+ # http://url1/
3625+
3626+(lxml y html5lib siempre lo hacen así; su comportamiento no puede ser
3627+configurado desde Beautiful Soup.)
3628+
3629+Si necesitas más, puedes pasar una función que sea llamada en cada valor duplicado::
3630+
3631+ def accumulate(attributes_so_far, key, value):
3632+ if not isinstance(attributes_so_far[key], list):
3633+ attributes_so_far[key] = [attributes_so_far[key]]
3634+ attributes_so_far[key].append(value)
3635+
3636+ soup = BeautifulSoup(markup, 'html.parser', on_duplicate_attribute=accumulate)
3637+ soup.a['href']
3638+ # ["http://url1/", "http://url2/"]
3639+
3640+
3641+`(Esto es una nueva característica en Beautiful Soup 4.9.1.)`
3642+
3643+Instanciar subclases personalizadas
3644+===================================
3645+
3646+Cuando un analizador indica a Beautiful Soup sobre una etiqueta o una cadena,
3647+Beautiful Soup instanciará un objeto :py:class:`Tag` o :py:class:`NavigableString`
3648+para contener esa información. En lugar de ese comportamiento por defecto,
3649+puedes indicar a Beautiful Soup que instancia `subclases` de :py:class:`Tag` o
3650+:py:class:`NavigableString`, subclases que defines con comportamiento
3651+personalizado::
3652+
3653+ from bs4 import Tag, NavigableString
3654+ class MyTag(Tag):
3655+ pass
3656+
3657+
3658+ class MyString(NavigableString):
3659+ pass
3660+
3661+
3662+ markup = "<div>some text</div>"
3663+ soup = BeautifulSoup(markup, 'html.parser')
3664+ isinstance(soup.div, MyTag)
3665+ # False
3666+ isinstance(soup.div.string, MyString)
3667+ # False
3668+
3669+ my_classes = { Tag: MyTag, NavigableString: MyString }
3670+ soup = BeautifulSoup(markup, 'html.parser', element_classes=my_classes)
3671+ isinstance(soup.div, MyTag)
3672+ # True
3673+ isinstance(soup.div.string, MyString)
3674+ # True
3675+
3676+
3677+Esto puede ser útil cuando se incorpore Beautiful Soup en un *framework*
3678+de pruebas.
3679+
3680+`(Esto es una nueva característica de Beautiful Soup 4.8.1.)`
3681+
3682+=========================
3683+ Resolución de problemas
3684+=========================
3685+
3686+.. _diagnose:
3687+
3688+``diagnose()``
3689+==============
3690+
3691+Si estás teniendo problemas para entender qué hace Beautiful Soup a un
3692+documento, pasa el documento a la función ``diagnose()``. (Nuevo en
3693+Beautiful Soup 4.2.0) Beautiful Soup imprimirá un informe mostrándote
3694+cómo manejan el documento diferentes analizadores, y te dirán si
3695+te falta un analizador que Beautiful Soup podría estar usando::
3696+
3697+ from bs4.diagnose import diagnose
3698+ with open("bad.html") as fp:
3699+ data = fp.read()
3700+
3701+ diagnose(data)
3702+
3703+ # Diagnostic running on Beautiful Soup 4.2.0
3704+ # Python version 2.7.3 (default, Aug 1 2012, 05:16:07)
3705+ # I noticed that html5lib is not installed. Installing it may help.
3706+ # Found lxml version 2.3.2.0
3707+ #
3708+ # Trying to parse your data with html.parser
3709+ # Here's what html.parser did with the document:
3710+ # ...
3711+
3712+Tan solo mirando a la salida de diagnose() puede mostrate cómo resolver
3713+el problema. Incluso si no, puedes pegar la salida de ``diagnose()``
3714+cuando pidas ayuda.
3715+
3716+Errores analizando un documento
3717+===============================
3718+
3719+Hay dos tipos diferentes de errores de análisis. Hay veces en que
3720+se queda colgado, donde le das a Beautiful Soup un documento y
3721+lanza una excepción, normalmente un ``HTMLParser.HTMLParseError``. Y hay
3722+comportamientos inesperados, donde un árbol analizado de Beautiful Soup
3723+parece muy diferente al documento usado para crearlo.
3724+
3725+Casi ninguno de estos problemas resultan ser problemas con Beautiful Soup.
3726+Esto no es porque Beautiful Soup sea una increíble y bien escrita pieza
3727+de software. Es porque Beautiful Soup no incluye ningún código de
3728+análisis. En lugar de eso, depende de análisis externos. Si un analizador
3729+no está funcionando en un documento concreto, la mejor solución es probar
3730+con otro analizador. Échale un vistazo a `Instalar un analizador`_ para
3731+detalles y una comparativa de analizadores.
3732+
3733+Los errores de análisis más comunes son ``HTMLParser.HTMLParseError:
3734+malformed start tag`` y ``HTMLParser.HTMLParseError: bad end
3735+tag``. Ambos son generados por la librería del analizador HTML
3736+incluido en Python, y la solución es :ref:`instalar lxml o html5lib.
3737+<parser-installation>`
3738+
3739+El comportamiento inesperado más común es que no puedas encontrar
3740+una etiqueta que sabes que está en el documento. La viste llegar, pero
3741+``find_all()`` devuelve ``[]`` o ``find()`` devuelve ``None``. Esto
3742+es otro problema común con el analizador HTML integrado en Python, el cual
3743+a veces omite etiquetas que no entiende. De nuevo, la mejor solución es
3744+:ref:`instalar lxml o html5lib. <parser-installation>`.
3745+
3746+Problemas de incompatibilidad de versiones
3747+==========================================
3748+
3749+* ``SyntaxError: Invalid syntax`` (on the line ``ROOT_TAG_NAME =
3750+ '[document]'``): Causado por ejecutar una version antigua de Beautiful
3751+ Soup de Python 2 bajo Python 3, sin convertir el código.
3752+
3753+* ``ImportError: No module named HTMLParser`` - Causado por ejecutar
3754+ una version antigua de Beautiful Soup de Python 2 bajo Python 3.
3755+
3756+* ``ImportError: No module named html.parser`` - Causado por ejecutar
3757+ una version de Beautiful Soup de Python 3 bajo Python 2.
3758+
3759+* ``ImportError: No module named BeautifulSoup`` - Causado por ejecutar
3760+ código de Beautiful Soup 3 en un sistema que no tiene BS3 instalado. O
3761+ al escribir código de Beautiful Soup 4 sin saber que el nombre del paquete
3762+ se cambió a ``bs4``.
3763+
3764+* ``ImportError: No module named bs4`` - Causado por ejecutar código de
3765+ Beautiful Soup 4 en un sistema que no tiene BS4 instalado.
3766+
3767+.. _parsing-xml:
3768+
3769+Analizar XML
3770+============
3771+
3772+Por defecto, Beautiful Soup analiza documentos HTML. Para analizar
3773+un documento como XML, pasa "xml" como el segundo argumento al
3774+constructor :py:class:`BeautifulSoup`::
3775+
3776+ soup = BeautifulSoup(markup, "xml")
3777+
3778+Necesitarás :ref:`tener lxml instalado <parser-installation>`.
3779+
3780+Otros problemas de análisis
3781+===========================
3782+
3783+* Si tu script funciona en un ordenador pero no en otro, o en un
3784+ entorno virtual pero no en otro, o fuera del entorno virtual
3785+ pero no dentro, es probable porque los dos entornos tienen
3786+ diferentes librerías de analizadores disponibles. Por ejemplo,
3787+ puedes haber desarrollado el script en un ordenador que solo
3788+ tenga html5lib instalado. Mira `Diferencias entre analizadores`_
3789+ por qué esto importa, y solucionar el problema especificando una
3790+ librería de análisis en el constructor de :py:class:`Beautiful Soup`.
3791+
3792+* Porque `las etiquetas y atributos de HTML son sensibles a mayúsculas
3793+ y minúsculas <http://www.w3.org/TR/html5/syntax.html#syntax>`_,
3794+ los tres analizadores HTML convierten los nombres de las etiquetas y
3795+ atributos a minúscula. Esto es, el marcado <TAG></TAG> se convierte
3796+ a <tag></tag>. Si quieres preservar la mezcla entre minúscula y
3797+ mayúscula o mantener las mayúsculas en etiquetas y atributos,
3798+ necesitarás :ref:`analizar el documento como XML. <parsing-xml>`
3799+
3800+.. _misc:
3801+
3802+Diversos
3803+========
3804+
3805+* ``UnicodeEncodeError: 'charmap' codec can't encode character
3806+ '\xfoo' in position bar`` (o cualquier otro
3807+ ``UnicodeEncodeError``) - Este problema aparece principalmente
3808+ en dos situaciones. Primero, cuando intentas mostrar un carácter
3809+ Unicode que tu consola no sabe cómo mostrar (mira `esta página en la
3810+ wiki de Python <http://wiki.python.org/moin/PrintFails>`_). Segundo,
3811+ cuando estás escribiendo en un archivo y pasas un carácter Unicode
3812+ que no se soporta en tu codificación por defecto. En este caso,
3813+ la solución más simple es codificar explícitamente la cadena Unicode
3814+ en UTF-8 con ``u.encode("utf8")``.
3815+
3816+* ``KeyError: [attr]`` - Causado por acceder a ``tag['attr']`` cuando
3817+ la etiqueta en cuestión no define el atributo ``'attr'``. Los
3818+ errores más comunes son ``KeyError: 'href'`` y ``KeyError: 'class``.
3819+ Usa ``tag.get('attr')`` si no estás seguro si ``attr`` está definido,
3820+ tal y como harías con un diccionario de Python.
3821+
3822+* ``AttributeError: 'ResultSet' object has no attribute 'foo'`` - Esto
3823+ normalmente ocurre cuando esperas que ``find_all()`` devuelva
3824+ una sola etiqueta o cadena. Pero ``find_all()`` devuelve una
3825+ `lista` de etiquetas y cadenas--un objeto ``ResultSet``. Tienes que
3826+ iterar sobre la lista y comprobar el ``.foo`` de cada uno, O, si solo
3827+ quieres un resultado, tienes que usar ``find()`` en lugar de
3828+ ``find_all()``.
3829+
3830+* ``AttributeError: 'NoneType' object has no attribute 'foo'`` - Esto
3831+ normalmente ocurre porque llamaste a ``find()`` y después intentaste
3832+ acceder al atributo ``.foo`` del resultado. Pero en tu caso, ``find()``
3833+ no encontró nada, así que devolvió ``None``, en lugar de devolver
3834+ una etiqueta o una cadena de caracteres. Necesitas averiguar por qué
3835+ ``find()`` no está devolviendo nada.
3836+
3837+* ``AttributeError: 'NavigableString' object has no attribute
3838+ 'foo'`` - Esto ocurre normalmente porque estás tratando una
3839+ cadena de caracteres como si fuese una etiqueta. Puedes estar iterando
3840+ sobre una lista, esperando que tan solo contenga etiquetas, pero en
3841+ realidad contiene tanto etiquetas como cadenas.
3842+
3843+
3844+Mejorar el rendimiento
3845+======================
3846+
3847+Beautiful Soup nunca será tan rápido como los analizadores en los que
3848+se basa. Si el tiempo de respuesta es crítico, si estás pagando por
3849+tiempo de uso por hora, o si hay alguna otra razón por la que el tiempo
3850+de computación es más valioso que el tiempo del programador, deberías
3851+olvidarte de Beautiful Soup y trabajar directamente sobre
3852+`lxml <http://lxml.de/>`_.
3853+
3854+Dicho esto, hay cosas que puedes hacer para aumentar la velocidad de
3855+Beautiful Soup. Si no estás usando lxml como el analizador que hay
3856+por debajo, mi consejo es que :ref:`empieces a usarlo <parser-installation>`.
3857+Beautiful Soup analiza documentos significativamente más rápido usando
3858+lxml que usando html.parser o html5lib.
3859+
3860+Puedes aumentar la velocidad de detección de codificación significativamente
3861+instalando la librería `cchardet <http://pypi.python.org/pypi/cchardet/>`_.
3862+
3863+`Analizar solo parte del documento`_ no te ahorrará mucho tiempo de análisis, pero puede
3864+ahorrar mucha memoria, y hará que `buscar` en el documento sea mucho más rápido.
3865+
3866+==============================
3867+ Traducir esta documentación
3868+==============================
3869+
3870+Nuevas traducciones de la documentación de Beautiful Soup se agradecen
3871+enormemente. Las traducciones deberían estar bajo la licencia del MIT, tal
3872+y como están Beautiful Soup y su documentación en inglés.
3873+
3874+Hay dos maneras para que tu traducción se incorpore a la base de código
3875+principal y al sitio de Beautiful Soup:
3876+
3877+1. Crear una rama del repositorio de Beautiful Soup, añadir tus
3878+ traducciones, y proponer una fusión (*merge*) con la rama principal, lo
3879+ mismo que se haría con una propuesta de código del código fuente.
3880+
3881+2. Enviar un mensaje al grupo de discusión de Beautiful Soup con un
3882+ enlace a tu traducción, o adjuntar tu traducción al mensaje.
3883+
3884+Utiliza la traducción china o portugués-brasileño como tu modelo. En
3885+particular, por favor, traduce el archivo fuente ``doc/source/index.rst``,
3886+en vez de la versión HTML de la documentación. Esto hace posible que la
3887+documentación se pueda publicar en una variedad de formatos, no solo HTML.
3888+
3889+==================
3890+ Beautiful Soup 3
3891+==================
3892+
3893+Beautiful Soup 3 es la serie de lanzamientos anterior, y no está siendo
3894+activamente desarrollada. Actualmente está empaquetada con las
3895+distribuciones de Linux más grandes:
3896+
3897+:kbd:`$ apt-get install python-beautifulsoup`
3898+
3899+También está publicada a través de PyPI como :py:class:`BeautifulSoup`.:
3900+
3901+:kbd:`$ easy_install BeautifulSoup`
3902+
3903+:kbd:`$ pip install BeautifulSoup`
3904+
3905+También puedes `descargar un tarball de Beautiful Soup 3.2.0
3906+<http://www.crummy.com/software/BeautifulSoup/bs3/download/3.x/BeautifulSoup-3.2.0.tar.gz>`_.
3907+
3908+Si ejecutaste ``easy_install beautifulsoup`` o ``easy_install BeautifulSoup``,
3909+pero tu código no funciona, instalaste por error Beautiful Soup 3. Necesitas
3910+ejecutar ``easy_install beautifulsoup4``.
3911+
3912+`La documentación de Beautiful Soup 3 está archivada online
3913+<http://www.crummy.com/software/BeautifulSoup/bs3/documentation.html>`_.
3914+
3915+Actualizar el código a BS4
3916+==========================
3917+
3918+La mayoría del código escrito con Beautiful Soup 3 funcionará
3919+con Beautiful Soup 4 con un cambio simple. Todo lo que debes hacer
3920+es cambiar el nombre del paquete de :py:class:`BeautifulSoup` a
3921+``bs4``. Así que esto::
3922+
3923+ from BeautifulSoup import BeautifulSoup
3924+
3925+se convierte en esto::
3926+
3927+ from bs4 import BeautifulSoup
3928+
3929+* Si obtienes el ``ImportError`` "No module named BeautifulSoup`", tu
3930+ problema es que estás intentando ejecutar código de Beautiful Soup 3,
3931+ pero solo tienes instalado Beautiful Soup 4.
3932+
3933+* Si obtienes el ``ImportError`` "No module named bs4", tu problema
3934+ es que estás intentando ejecutar código Beautiful Soup 4, pero solo
3935+ tienes Beautiful Soup 3 instalado.
3936+
3937+Aunque BS4 es mayormente compatible con la versión anterior BS3, la
3938+mayoría de sus métodos han quedado obsoletos y dados nuevos nombres
3939+para que `cumplan con PEP 8 <http://www.python.org/dev/peps/pep-0008/>`_.
3940+Hay muchos otros renombres y cambios, y algunos de ellos rompen
3941+con la compatibilidad hacia atrás.
3942+
3943+Esto es todo lo que necesitarás saber para convertir tu código y hábitos BS3 a
3944+BS4:
3945+
3946+Necesitas un analizador
3947+-----------------------
3948+
3949+Beautiful Soup 3 usaba el ``SGMLParser`` de Python, un módulo que
3950+fue obsoleto y quitado en Python 3.0. Beautiful Soup 4 usa
3951+``html.parser`` por defecto, pero puedes conectar lxml o html5lib
3952+y usar esos. Mira `Instalar un analizador`_ para una comparación.
3953+
3954+Como ``html.parser`` no es el mismo analizador que ``SGMLParser``,
3955+podrías encontrarte que Beautiful Soup 4 te de un árbol analizado
3956+diferente al que te da Beautiful Soup 3 para el mismo marcado. Si
3957+cambias ``html.parser`` por lxml o html5lib, puedes encontrarte
3958+que el árbol analizado también cambia. Si esto ocurre, necesitarás
3959+actualizar tu código de *scraping* para gestionar el nuevo árbol.
3960+
3961+Nombre de los métodos
3962+---------------------
3963+
3964+* ``renderContents`` -> ``encode_contents``
3965+* ``replaceWith`` -> ``replace_with``
3966+* ``replaceWithChildren`` -> ``unwrap``
3967+* ``findAll`` -> ``find_all``
3968+* ``findAllNext`` -> ``find_all_next``
3969+* ``findAllPrevious`` -> ``find_all_previous``
3970+* ``findNext`` -> ``find_next``
3971+* ``findNextSibling`` -> ``find_next_sibling``
3972+* ``findNextSiblings`` -> ``find_next_siblings``
3973+* ``findParent`` -> ``find_parent``
3974+* ``findParents`` -> ``find_parents``
3975+* ``findPrevious`` -> ``find_previous``
3976+* ``findPreviousSibling`` -> ``find_previous_sibling``
3977+* ``findPreviousSiblings`` -> ``find_previous_siblings``
3978+* ``getText`` -> ``get_text``
3979+* ``nextSibling`` -> ``next_sibling``
3980+* ``previousSibling`` -> ``previous_sibling``
3981+
3982+Algunos argumentos del constructor de Beautiful Soup fueron renombrados
3983+por la misma razón:
3984+
3985+* ``BeautifulSoup(parseOnlyThese=...)`` -> ``BeautifulSoup(parse_only=...)``
3986+* ``BeautifulSoup(fromEncoding=...)`` -> ``BeautifulSoup(from_encoding=...)``
3987+
3988+Renombré un método para compatibilidad con Python 3:
3989+
3990+* ``Tag.has_key()`` -> ``Tag.has_attr()``
3991+
3992+Renombré un atributo para usar terminología más precisa:
3993+
3994+* ``Tag.isSelfClosing`` -> ``Tag.is_empty_element``
3995+
3996+Renombré tres atributos para evitar usar palabras que tienen un significado
3997+especial en Python. A diferencia de otros, estos cambios no soportan
3998+*compatibilidad hacia atrás*. Si usaste estos atributos en BS3, tu código
3999+se romperá en BS4 hasta que lo cambies.
4000+
4001+* ``UnicodeDammit.unicode`` -> ``UnicodeDammit.unicode_markup``
4002+* ``Tag.next`` -> ``Tag.next_element``
4003+* ``Tag.previous`` -> ``Tag.previous_element``
4004+
4005+Estos métodos sobras desde la API de Beautiful Soup 2. Han quedado
4006+obsoletos desde 2006, y no deberían usarse en absoluto:
4007+
4008+* ``Tag.fetchNextSiblings``
4009+* ``Tag.fetchPreviousSiblings``
4010+* ``Tag.fetchPrevious``
4011+* ``Tag.fetchPreviousSiblings``
4012+* ``Tag.fetchParents``
4013+* ``Tag.findChild``
4014+* ``Tag.findChildren``
4015+
4016+
4017+Generadores
4018+-----------
4019+
4020+Le di a los generadores nombres que cumplan con PEP 8, y se transformaron
4021+en propiedades:
4022+
4023+* ``childGenerator()`` -> ``children``
4024+* ``nextGenerator()`` -> ``next_elements``
4025+* ``nextSiblingGenerator()`` -> ``next_siblings``
4026+* ``previousGenerator()`` -> ``previous_elements``
4027+* ``previousSiblingGenerator()`` -> ``previous_siblings``
4028+* ``recursiveChildGenerator()`` -> ``descendants``
4029+* ``parentGenerator()`` -> ``parents``
4030+
4031+Así que en lugar de esto::
4032+
4033+ for parent in tag.parentGenerator():
4034+ ...
4035+
4036+Puedes escribir esto::
4037+
4038+ for parent in tag.parents:
4039+ ...
4040+
4041+(Pero el código antiguo seguirá funcionando).
4042+
4043+Algunos de los generadores solían devolver ``None`` después de que hayan
4044+terminado, y después paran. Eso era un error. Ahora el generador tan solo
4045+para.
4046+
4047+Hay dos nuevos generadores, :ref:`.strings y .stripped_strings
4048+<string-generators>`. ``.strings`` devuelve objetos NavigableString,
4049+y ``.stripped_strings`` devuelve cadenas de Python cuyos espacios
4050+en blanco al comienzo y al final han sido quitados.
4051+
4052+XML
4053+---
4054+
4055+Ya no hay una clase ``BeautifulStoneSoup`` para analizar XML. Para
4056+analizar XML pasas "xml" como el segundo argumento del constructor
4057+de :py:class:`BeautifulSoup`. Por la misma razón, el constructor
4058+de :py:class:`BeautifulSoup` ya no reconoce el argumento ``isHTML``.
4059+
4060+La gestión de Beautiful Soup sobre las etiquetas XML sin elementos ha sido
4061+mejorada. Previamente cuando analizabas XML tenías que indicar
4062+explícitamente qué etiquetas eran consideradas etiquetas sin elementos.
4063+El argumento ``selfClosingTags`` al constructor ya no se reconoce.
4064+En lugar de ello, Beautiful Soup considera cualquier etiqueta vacía como
4065+una etiqueta sin elementos. Si añades un hijo a una etiqueta sin elementos,
4066+deja de ser una etiqueta sin elementos.
4067+
4068+Entidades
4069+---------
4070+
4071+Una entidad HTML o XML entrante siempre se convierte al correspondiente
4072+carácter Unicode. Beautiful Soup 3 tenía varias formas solapadas para
4073+gestionar entidades, las cuales se han eliminado. El constructor de
4074+:py:class:`BeautifulSoup` ya no reconoce los argumentos ``smartQuotesTo``
4075+o ``convertEntities`` (`Unicode, Dammit`_ aún tiene ``smart_quotes_to``,
4076+pero por defecto ahora transforma las comillas inteligentes a Unicode).
4077+Las constantes ``HTML_ENTITIES``, ``XML_ENTITIES``, y ``XHTML_ENTITIES``
4078+han sido eliminadas, ya que configuran una característica (transformando
4079+algunas pero no todas las entidades en caracteres Unicode) que ya no
4080+existe.
4081+
4082+Si quieres volver a convertir caracteres Unicode en entidades HTML
4083+a la salida, en lugar de transformarlos a caracteres UTF-8, necesitas
4084+usar un :ref:`*formatter* de salida <output_formatters>`.
4085+
4086+Otro
4087+----
4088+
4089+:ref:`Tag.string <.string>` ahora funciona recursivamente. Si una
4090+etiqueta A contiene una sola etiqueta B y nada más, entonces
4091+A.string es el mismo que B.string (Antes, era ``None``).
4092+
4093+Los `atributos multivaluados`_ como ``class`` tienen listas de cadenas
4094+de caracteres como valores, no cadenas. Esto podría afectar la manera
4095+en la que buscas por clases CSS.
4096+
4097+Objetos :py:class:`Tag` ahora implementan el método ``__hash__``, de tal
4098+manera que dos objetos :py:class:`Tag` se consideran iguales si generan
4099+el mismo marcado. Esto puede cambiar el comportamiento de tus scripts
4100+si insertas los objetos :py:class:`Tag` en un diccionario o conjunto.
4101+
4102+Si pasas a unos de los métodos ``find*`` una :ref:`cadena <string>` y
4103+un argumento específico de una etiqueta como :ref:`name <name>`, Beautiful
4104+Soup buscará etiquetas que casen con tu criterio específico de la etiqueta
4105+y cuyo :ref:`Tag.string <.string>` case con tu valor para la :ref:`cadena <string>`.
4106+`No` encontrará las cadenas mismas. Anteriormente, Beautiful Soup ignoraba el
4107+argumento específico de la etiqueta y buscaba por cadenas de caracteres.
4108+
4109+El constructor de :py:class:`Beautiful Soup` ya no reconoce el argumento
4110+`markupMassage`. Es ahora responsabilidad del analizador gestionar el marcado
4111+correctamente.
4112+
4113+Los analizadores alternativos, que rara vez se utilizaban, como
4114+``ICantBelieveItsBeautifulSoup`` y ``BeautifulSOAP`` se han eliminado.
4115+Ahora es decisión del analizador saber cómo gestionar marcado ambiguo.
4116+
4117+El método ``prettify()`` ahora devuelve una cadena Unicode, no un bytestring.

Subscribers

People subscribed via source and target branches