Skip to main content

parsel_text

parsel_text

CI PyPI version Python versions License: MIT DeepWiki

parsel_text is a Python library designed to simplify the extraction of text data from HTML or XML documents using XPath queries on parsel Selector objects. It provides a straightforward interface to obtain and optionally fix mojibake (garbled text due to encoding issues).

Installation

To install parsel-text, use pip:

pip install parsel-text

Usage

Function: get_xpath_text

This is the main function of the library, designed to extract all text results from an XPath query on a parsel Selector object.

Parameters

  • parsel_sel: What to run the XPath on: a parsel.Selector, a parsel.SelectorList or a Scrapy response.
  • xpath (str): The XPath query string to specify the text extraction path.
  • fix_mojibake (bool, optional): A flag to indicate whether to fix mojibake issues in the extracted text. Default is True.
  • guess_layout (bool, optional): Join the text the way a browser lays it out instead of one text node per line (see Layout). Default is False.

Returns

  • str: A string containing the concatenated text results from the specified XPath query.

Example

Here's a simple example of how to use the get_xpath_text function:

from parsel import Selector
from parsel_text import get_xpath_text

html_content = """
<html>
  <body>
    <div id="content">
      <p>Hello, world!</p>
      <p>Welcome to the parsel_text library.</p>
    </div>
  </body>
</html>
"""

# Create a parsel Selector object
selector = Selector(text=html_content)

# Define the XPath query
xpath_query = "//div[@id='content']/p//text()"

# Extract text using the get_xpath_text function
extracted_text = get_xpath_text(parsel_sel=selector, xpath=xpath_query)

print(extracted_text)

Output

Hello, world!
Welcome to the parsel_text library.

What gets extracted

When the XPath selects elements, you get the text inside them:

  • Text inside <script>, <style>, <noscript> and <template>, and HTML comments, is dropped.
  • Whitespace is collapsed, except inside <pre> and <textarea>.
  • When the XPath matches nested elements (//div, for example), their text appears only once.
  • Mojibake is fixed with ftfy.fix_encoding, which leaves the rest of the text as is (curly quotes, ligatures, full-width characters...). Pass fix_mojibake=False to skip it.

When the XPath selects text nodes (.../text()), you get exactly those nodes with their whitespace collapsed, even inside <script>.

Layout

By default every text node goes on its own line, so a sentence with a <b> or a link in the middle comes out split. With guess_layout=True the text is joined the way a browser lays it out:

  • Inline tags (<b>, <a>, <span>...) keep the original spacing.
  • Block tags (<div>, <li>, <tr>...) and <br> start a new line.
  • Paragraphs, headings and lists are separated by a blank line.
  • Table cells are separated by a space, and <pre> is kept verbatim.

Only whitespace changes: the extracted text is the same. It is meant for HTML.

from parsel import Selector
from parsel_text import get_xpath_text

selector = Selector(text="""
<p><b>Total</b>: 42 (<a href="#">see all</a>).</p>
<ul><li>Python</li><li>Scrapy</li></ul>
""")

get_xpath_text(parsel_sel=selector, xpath="//body")
# 'Total\n: 42 (\nsee all\n).\nPython\nScrapy'

get_xpath_text(parsel_sel=selector, xpath="//body", guess_layout=True)
# 'Total: 42 (see all).\n\nPython\nScrapy'

Function: get_css_text

Same as get_xpath_text, but takes a CSS selector (css) instead of an XPath. The other parameters are the same.

from parsel import Selector
from parsel_text import get_css_text

selector = Selector(text='<div class="body"><p>Hello <b>world</b>.</p><p>Bye.</p></div>')

get_css_text(parsel_sel=selector, css="div.body", guess_layout=True)
# 'Hello world.\n\nBye.'

get_css_text(parsel_sel=selector, css="div.body ::text", guess_layout=True)
# 'Hello world.\n\nBye.'

Since the library runs the query itself, ::text gets the layout too. Like .../text() XPaths, ::text keeps the text inside <script>: select the element (div.body) to drop it.

Function: get_selector_text

Extracts the text of a selection you already made: a Selector or a SelectorList from .css(), .xpath() or a chain of them. It takes fix_mojibake and guess_layout too.

from parsel import Selector
from parsel_text import get_selector_text

selector = Selector(text="""
<div class="card"><h2>First</h2><div class="body"><p>Hello <b>world</b>.</p><p>Second paragraph.</p></div></div>
<div class="card"><h2>Second</h2><div class="body"><p>More text.</p></div></div>
""")

for card in selector.css("div.card"):
    title = get_selector_text(parsel_sel=card.css("h2"))
    body = get_selector_text(parsel_sel=card.css("div.body"), guess_layout=True)
    print((title, body))
# ('First', 'Hello world.\n\nSecond paragraph.')
# ('Second', 'More text.')

Selecting elements gives the same result as get_xpath_text. Text and attribute selections (::text, ::attr()) behave like .../text() XPaths: one piece per line, whitespace collapsed even inside <pre>, script text included and no layout. To get the exclusions and the layout, select the element (div.body), not its text (div.body ::text).

A Scrapy response is not a selection: pass response.css(...) or response.selector, or use get_xpath_text.

Function: get_results_row_text

Takes the same parameters as get_xpath_text, but returns one string per match instead of joining them: N matches give N strings, in document order. Each match keeps all of its text, even when matches are nested.

from parsel import Selector
from parsel_text import get_results_row_text

selector = Selector(text="<ul><li>Python</li><li>Scrapy <b>2.x</b></li></ul>")

get_results_row_text(parsel_sel=selector, xpath="//li")
# ['Python', 'Scrapy\n2.x']

get_results_row_text(parsel_sel=selector, xpath="//li", guess_layout=True)
# ['Python', 'Scrapy 2.x']

Function: get_bs4_soup_text

Extracts the text of a BeautifulSoup object with the same rules, and also takes fix_mojibake and guess_layout. parsel_text no longer depends on BeautifulSoup: this function works with anything whose str() is HTML. To install BeautifulSoup along with the library, use the bs4 extra:

pip install "parsel-text[bs4]"

Contributing

Contributions are welcome! If you find a bug or have a feature request, please open an issue on the GitHub repository.

License

This project is licensed under the MIT License. See the LICENSE file for details.

Metadata

Release files for parsel-text 2.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for parsel-text 2.2.1
File Size Uploaded
parsel_text-2.2.1.tar.gz 20.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for parsel-text 2.2.1
File Interpreter ABI Platform
parsel_text-2.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 31.0 kB

Release files / parsel_text-2.2.1.tar.gz

Download URL parsel_text-2.2.1.tar.gz
Size 20.4 kB
Tags Source
SHA-256 checksum
How to use checksums
97f9ee793687502415b49399dacc0b62324017b7006eedc7d75e934074640e4f
BLAKE2b-256 checksum
How to use checksums
d770753013558a225e52b580fe354078e2f1fbe60ab396f24b8f69df53de97a7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release files / parsel_text-2.2.1-py3-none-any.whl

Download URL parsel_text-2.2.1-py3-none-any.whl
Size 10.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
da0fb9a300a4135e054fecc096f388cfea0e1c8743a7a0030ee0d45bf8852fca
BLAKE2b-256 checksum
How to use checksums
3e85be75484a46ac705336def586cd64361e8039060e81a0b005718509c4de57
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

2.2.1 This release

2 release files

2.2.0

2 release files

2.1.0

2 release files

2.0.2

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.2

2 release files

1.1

2 release files

1.0

2 release files

0.9

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page