356

I am currently using Beautiful Soup to parse an HTML file and calling get_text(), but it seems like I'm being left with a lot of \xa0 Unicode representing spaces. Is there an efficient way to remove all of them in Python 2.7, and change them into spaces? I guess the more generalized question would be, is there a way to remove Unicode formatting?

I tried using: line = line.replace(u'\xa0',' '), as suggested by another thread, but that changed the \xa0's to u's, so now I have "u"s everywhere instead. ):

EDIT: The problem seems to be resolved by str.replace(u'\xa0', ' ').encode('utf-8'), but just doing .encode('utf-8') without replace() seems to cause it to spit out even weirder characters, \xc2 for instance. Can anyone explain this?

ivanleoncz
  • 9,070
  • 7
  • 57
  • 49
zhuyxn
  • 6,671
  • 9
  • 38
  • 44
  • tried that already, 'ascii' codec can't decode byte 0xa0 in position 0: ordinal not in range(128) – zhuyxn Jun 12 '12 at 09:19
  • 19
    embrace Unicode. Use `u''`s instead of `''`s. :-) – jpaugh Jun 12 '12 at 09:26
  • 2
    tried using str.replace(u'\xa0', ' ') but got "u"s everywhere instead of \xa0s :/ – zhuyxn Jun 12 '12 at 09:30
  • If the string is the unicode one, you have to use the `u' '` replacement, not the `' '`. Is the original string the unicode one? – pepr Jun 12 '12 at 10:51

16 Answers16

410

\xa0 is actually non-breaking space in Latin1 (ISO 8859-1), also chr(160). You should replace it with a space.

string = string.replace(u'\xa0', u' ')

When .encode('utf-8'), it will encode the unicode to utf-8, that means every unicode could be represented by 1 to 4 bytes. For this case, \xa0 is represented by 2 bytes \xc2\xa0.

Read up on http://docs.python.org/howto/unicode.html.

Please note: this answer in from 2012, Python has moved on, you should be able to use unicodedata.normalize now

TFD
  • 23,890
  • 2
  • 34
  • 51
samwize
  • 25,675
  • 15
  • 141
  • 186
  • 16
    I don't know a huge amount about Unicode and character encodings.. but it seems like [unicodedata.normalize](http://docs.python.org/2/library/unicodedata.html#unicodedata.normalize) would be more appropriate than str.replace – dbr Sep 09 '13 at 07:45
  • Yours is workable advice for strings, but note that all references to this string will also need to be replaced. For example, if you have a program that opens files, and one of the files has a non-breaking space in its name, you will need to *rename* that file in addition to doing this replacement. –  Sep 23 '14 at 10:52
  • 4
    [U+00a0 is a non-breakable space Unicode character](http://codepoints.net/U+00a0) that can be encoded as `b'\xa0'` byte in latin1 encoding, as two bytes `b'\xc2\xa0'` in utf-8 encoding. It can be represented as ` ` in html. – jfs Jan 20 '15 at 12:39
  • 4
    When I try this, I get `UnicodeDecodeError: 'ascii' codec can't decode byte 0xa0 in position 397: ordinal not in range(128)`. – jds May 28 '15 at 22:15
  • I tried this code on a list of strings, it didn't do anything, and the \xa0 character remained. If I reencoded my text file to UTF-8, the character would appear as an upper case A with a carrot on it's head, and I encoded it in Unicode the Python interpreter crashed. – Mushroom Man Jul 20 '16 at 22:02
  • @dbr `unicodedata` does **not** replace `\xa0` with `NFC` (which properly retains letters with accent such as `é`). Example: `unicodedata.normalize("NFC", "LEFT\xa0RIGHT") == "LEFT\xa0RIGHT"`. – Jean Monet Apr 01 '22 at 08:21
325

There's many useful things in Python's unicodedata library. One of them is the .normalize() function.

Try:

new_str = unicodedata.normalize("NFKD", unicode_str)

Replacing NFKD with any of the other methods listed in the link above if you don't get the results you're after.

Jamie
  • 3,358
  • 1
  • 9
  • 6
  • This did the trick. Had some HTML generated by... Microsoft Word with lots of weird unicode characters and this somehow cleaned them all. – José Tomás Tocino Jun 04 '17 at 19:06
  • 4
    Not so sure, you may want `normalize('NFKD', '1º\xa0dia')` to return '1º dia' but it returns '1o dia' – Faccion Nov 08 '17 at 14:58
  • 5
    here is the [docs about `unicodedata.normalize`](https://docs.python.org/3/library/unicodedata.html#unicodedata.normalize) – TT-- Dec 04 '17 at 15:04
  • 3
    ah, if text is 'KOREAN', do not try this. 글자가 전부 깨져버리네요. – Cho Oct 17 '19 at 09:05
  • 3
    This solution changes Russian letter `й` to an identically looking sequence of two unicode characters. The problem here is that strings that used to be equal do not match anymore. Fix: use `"NFKC"` instead of `"NFKD"`. – Markus Apr 21 '20 at 19:23
  • It doesn't chatch the 'soft hyphen' (-) which is '\xad' in Latin1. Are there any trick to also catch this symbol? – the_economist Jul 10 '20 at 11:06
  • @Markus: The same applies to the German Umlaute ö, ü and ä. 'NFKC' is required instead of 'NFKD'. – the_economist Jul 10 '20 at 11:27
  • 3
    This is awesome. It changes the one-letter string `﷼` to the four-letter string `ریال` that it actually is. So it's much easier to replace when needed. You'd normalize and then replace, without having to care which one it was. `normalize("NFKD", "﷼").replace("ریال", '')`. – Amir Shabani Apr 29 '21 at 07:55
39

After trying several methods, to summarize it, this is how I did it. Following are two ways of avoiding/removing \xa0 characters from parsed HTML string.

Assume we have our raw html as following:

raw_html = '<p>Dear Parent, </p><p><span style="font-size: 1rem;">This is a test message, </span><span style="font-size: 1rem;">kindly ignore it. </span></p><p><span style="font-size: 1rem;">Thanks</span></p>'

So lets try to clean this HTML string:

from bs4 import BeautifulSoup
raw_html = '<p>Dear Parent, </p><p><span style="font-size: 1rem;">This is a test message, </span><span style="font-size: 1rem;">kindly ignore it. </span></p><p><span style="font-size: 1rem;">Thanks</span></p>'
text_string = BeautifulSoup(raw_html, "lxml").text
print text_string
#u'Dear Parent,\xa0This is a test message,\xa0kindly ignore it.\xa0Thanks'

The above code produces these characters \xa0 in the string. To remove them properly, we can use two ways.

Method # 1 (Recommended): The first one is BeautifulSoup's get_text method with strip argument as True So our code becomes:

clean_text = BeautifulSoup(raw_html, "lxml").get_text(strip=True)
print clean_text
# Dear Parent,This is a test message,kindly ignore it.Thanks

Method # 2: The other option is to use python's library unicodedata, specifically unicodedata.normalize

import unicodedata
text_string = BeautifulSoup(raw_html, "lxml").text
clean_text = unicodedata.normalize("NFKD",text_string)
print clean_text
# u'Dear Parent,This is a test message,kindly ignore it.Thanks'

I have also detailed these methods on this blog which you may want to refer.

Nate Anderson
  • 18,334
  • 18
  • 100
  • 135
Ali Raza Bhayani
  • 3,045
  • 26
  • 20
  • 4
    get_text(strip=True) really did a trick. Thanks m8 – ChewChew Nov 24 '21 at 18:57
  • this is very specific for raw html returning unicode after cleaning with bs4 or regex. Works perfectly, but it will not remove line breaks or tabs – Y4RD13 May 09 '22 at 12:18
30

Try using .strip() at the end of your line line.strip() worked well for me

user3590113
  • 517
  • 7
  • 13
20

try this:

string.replace('\\xa0', ' ')
user278064
  • 9,982
  • 1
  • 33
  • 46
  • 6
    @RyanMartin: this replaces **four bytes**: `len(b'\\xa0') == 4` but `len(b'\xa0') == 1`. If possible; you should fix upstream that generates these escapes. – jfs Jan 20 '15 at 12:43
  • 4
    This solution worked for me: `string.replace('\xa0', ' ')` – Jenya Pu Jul 04 '20 at 14:31
17

Python recognize it like a space character, so you can split it without args and join by a normal whitespace:

line = ' '.join(line.split())
Max
  • 1,634
  • 1
  • 19
  • 36
15

I ran into this same problem pulling some data from a sqlite3 database with python. The above answers didn't work for me (not sure why), but this did: line = line.decode('ascii', 'ignore') However, my goal was deleting the \xa0s, rather than replacing them with spaces.

I got this from this super-helpful unicode tutorial by Ned Batchelder.

Community
  • 1
  • 1
  • 15
    You are now removing anything that isn't a ASCII character, you are probably masking your actual problem. Using `'ignore'` is like shoving through the shift stick even though you don't understand how the clutch works.. – Martijn Pieters Dec 11 '12 at 20:58
  • @MartijnPieters The linked unicode tutorial is good, but you are completely correct - `str.encode(..., 'ignore')` is the Unicode-handling equivalent of `try: ... except: ...`. While it might hide the error message, it rarely solves the problem. – dbr Sep 09 '13 at 07:43
  • 2
    for some purposes like dealing with EMAIL or URLS it seems perfect to use `.decode('ascii', 'ignore')` – andilabs Dec 12 '14 at 10:15
  • 2
    [samwize's answer](http://stackoverflow.com/a/11566398/4279) didn't work for you because it works on **Unicode** strings. `line.decode()` in your answer suggests that your input is a **bytestring** (you should not call `.decode()` on a Unicode string (to enforce it, the method is removed in Python 3). I don't understand how it is possible to see [the tutorial that you've linked in your answer](http://nedbatchelder.com/text/unipain.html) and miss the difference between bytes and Unicode (do not mix them). – jfs Jan 20 '15 at 12:49
13

Try this code

import re
re.sub(r'[^\x00-\x7F]+','','paste your string here').decode('utf-8','ignore').strip()
shiva
  • 429
  • 1
  • 6
  • 18
9

I end up here while googling for the problem with not printable character. I use MySQL UTF-8 general_ci and deal with polish language. For problematic strings I have to procced as follows:

text=text.replace('\xc2\xa0', ' ')

It is just fast workaround and you probablly should try something with right encoding setup.

andilabs
  • 22,159
  • 14
  • 114
  • 151
  • 2
    this works if `text` is a bytestring that represents a text encoded using utf-8. If you are working with text; decode it to Unicode first (`.decode('utf-8')`) and encode it to a bytestring only at the very end (if API does not support Unicode directly e.g., `socket`). All intermediate operations on the text should be performed on Unicode. – jfs Jan 20 '15 at 12:57
7

In Beautiful Soup, you can pass get_text() the strip parameter, which strips white space from the beginning and end of the text. This will remove \xa0 or any other white space if it occurs at the start or end of the string. Beautiful Soup replaced an empty string with \xa0 and this solved the problem for me.

mytext = soup.get_text(strip=True)
shauryachats
  • 9,975
  • 4
  • 35
  • 48
Mark
  • 71
  • 1
  • 2
  • 11
    `strip=True` works only if ` ` is at the beginning or end of each bit of text. It won't remove the space if it is inbetween other characters in the text. – jfs Jan 20 '15 at 13:01
7

In Python, \xa0 is a character escape sequence that represents a non-breaking space.

A non-breaking space is a space character that prevents line breaks and word wrapping between two words separated by it.

You can get rid of them by running replace on a string which contains them:

my_string.replace('\xa0', '') # no more xa0
8bitjunkie
  • 12,793
  • 9
  • 57
  • 70
4

0xA0 (Unicode) is 0xC2A0 in UTF-8. .encode('utf8') will just take your Unicode 0xA0 and replace with UTF-8's 0xC2A0. Hence the apparition of 0xC2s... Encoding is not replacing, as you've probably realized now.

dda
  • 6,030
  • 2
  • 25
  • 34
3

You can try string.strip()
It worked for me! :)

STA
  • 30,729
  • 8
  • 45
  • 59
1

Generic version with the regular expression (It will remove all the control characters):

import re
def remove_control_chart(s):
    return re.sub(r'\\x..', '', s)
ranaFire
  • 7
  • 5
1

This is how I solved this issue as I encountered \xao in html encoded string.

I discovered a None breaking space is inserted to ensure that a word and subsequent HTML markup is not separated due to resizing of a page.

This presents a problem for the parsing code as it introduced codec encoding issues. What made it hard was that we are not privy to the encoding used. From Windows machines it can be latin-1 or CP1252 (Western ISO), but more recent OSes have standardized to UTF-8. By normalizing unicode data, we strip \xa0

my_string = unicodedata.normalize('NFKD', my_string).encode('ASCII', 'ignore')
Amro Younes
  • 1,261
  • 2
  • 16
  • 34
0

Was facing the same issue, got this done and went well.

df = df.replace(u'\xa0', u'', regex=True)

All instances of \xa0 get replaced.

petezurich
  • 9,280
  • 9
  • 43
  • 57
Chirag
  • 13
  • 5