Beautiful Soup — `findAll` не захватывает все теги в SVG (`ElementTree` делает)

Я пытался создать картограмму, изменив карту SVG с изображением всех округов США. Основной подход отражен в Передача данных. Поскольку SVG — это, по сути, просто XML, этот подход использует парсер BeautifulSoup.

Дело в том, что парсер не захватывает все элементы path в файле SVG. Следующее зафиксировало только 149 путей (из более чем 3000):

#Open SVG file
svg=open(shp_dir+'USA_Counties_with_FIPS_and_names.svg','r').read()

#Parse SVG
soup = BeautifulSoup(svg, selfClosingTags=['defs','sodipodi:namedview'])

#Identify counties
paths = soup.findAll('path')

len(paths)

Однако я знаю, что их гораздо больше, как по физическому осмотру, так и по тому факту, что Методы ElementTree фиксируют 3143 пути с помощью следующей процедуры:

#Parse SVG
tree = ET.parse(shp_dir+'USA_Counties_with_FIPS_and_names.svg')

#Capture element
root = tree.getroot()

#Compile list of IDs from file
ids=[]
for child in root:
    if 'path' in child.tag:
        ids.append(child.attrib['id'])

len(ids)

Я еще не понял, как писать из объекта ElementTree таким образом, чтобы не все испортилось.

#Define style template string
style='font-size:12px;fill-rule:nonzero;stroke:#FFFFFF;stroke-opacity:1;'+\
        'stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;'+\
        'stroke-linecap:butt;marker-start:none;stroke-linejoin:bevel;fill:'

#For each path...
for child in root:
    #...if it is a path....
    if 'path' in child.tag:
        try:
            #...update the style to the new string with a county-specific color...
            child.attrib['style']=style+col_map[child.attrib['id']]
        except:
            #...if it's not a county we have in the ACS, leave it alone
            child.attrib['style']=style+'#d0d0d0'+'\n'

#Write modified SVG to disk
tree.write(shp_dir+'mhv_by_cty.svg')

Приведенная выше процедура модификации/записи дает это чудовище:

Уродливая средняя стоимость дома по округам

Мой главный вопрос таков: почему BeautifulSoup не удалось захватить все теги path? Во-вторых, почему изображение, измененное с помощью ElementTree объектов, должно иметь все эти внеклассные занятия? Мы будем очень признательны за любые советы.


person Marvin Ward Jr    schedule 19.01.2015    source источник
comment
Используя BeautifulSoup 4.3.2, я запустил svg_soup = BeautifulSoup(svg); paths = svg_soup.find_all('path'); len(paths), который выдал 3143. Возможно, вам нужно обновить bs4?   -  person MattDMo    schedule 19.01.2015


Ответы (2)


Ответ Alexce правильный для вашего первого вопроса. Что касается вашего второго вопроса:

почему изображение, измененное с помощью объектов ElementTree, должно иметь все эти внеклассные занятия?"

ответ довольно прост - не каждый элемент <path> рисует округ. В частности, есть два элемента, один с id="State_Lines" и один с id="separator", которые следует исключить. Вы не предоставили свой набор данных цветов, поэтому я просто использовал генератор случайных шестнадцатеричных цветов (адаптированный из здесь) для каждого округа, затем использовал lxml для разбора XML-файла .svg и повторения каждого элемента <path>, пропуская упомянутые выше:

from lxml import etree as ET
import random

def random_color():
    r = lambda: random.randint(0,255)
    return '#%02X%02X%02X' % (r(),r(),r())

new_style = 'font-size:12px;fill-rule:nonzero;stroke:#FFFFFF;stroke-opacity:1;stroke-width:0.1;stroke-miterlimit:4;stroke-dasharray:none;stroke-linecap:butt;marker-start:none;stroke-linejoin:bevel;fill:'

tree = ET.parse('USA_Counties_with_FIPS_and_names.svg')
root = tree.getroot()
for child in root:
    if 'path' in child.tag and child.attrib['id'] not in ["separator", "State_Lines"]:
        child.attrib['style'] = new_style + random_color()

tree.write('counties_new.svg')

в результате получается это красивое изображение:

person MattDMo    schedule 19.01.2015
comment
Работал как шарм. Однозначно оплошность, меня подкинул паттерн, не соответствующий каким-то определенным границам. - person Marvin Ward Jr; 19.01.2015

Вам необходимо сделать следующее:

  • обновить до beautifulsoup4:

    pip install beautifulsoup4 -U
    
  • импортировать как:

    from bs4 import BeautifulSoup
    
  • установить последний модуль lxml:

    pip install lxml -U
    
  • явно указать lxml в качестве парсера:

    soup = BeautifulSoup(svg, 'lxml')
    

Демо:

>>> from bs4 import BeautifulSoup
>>> 
>>> svg = open('USA_Counties_with_FIPS_and_names.svg','r').read()
>>> soup = BeautifulSoup(svg, 'lxml')
>>> paths = soup.findAll('path')
>>> len(paths)
3143
person alecxe    schedule 19.01.2015