Я пытаюсь создать веб-скребок. Мой парсер должен найти все строки, соответствующие выбранным тегам, и сохранить их в том же порядке, что и исходный HTML в новый файл file.md
.
Теги указываются в массиве:
list_of_tags_you_want_to_scrape = ['h1', 'h2', 'h3', 'p', 'li']
то это дает мне только содержимое указанного тега:
soup_each_html = BeautifulSoup(particular_page_content, "html.parser")
inner_content = soup_each_html.find("article", "container")
скажем, что это результат:
<article class = "container">
<h1>this is headline 1</h1>
<p>this is paragraph</p>
<h2>this is headline 2</h2>
<a href = "bla.html">this won't be shown bcs 'a' tag is not in the array</a>
</article>
то у меня есть метод, который отвечает за запись строки в файл file.md
, если тег из массива существует в содержимом
with open("file.md", 'a+') as f:
for tag in list_of_tags_you_want_to_scrape:
inner_content_tag = inner_content.find_all(tag)
for x in inner_content_tag:
f.write(str(x))
f.write("\n")
и это так. Но проблема в том, что он проходит через массив (для каждого) и сохраняет сначала все <h1>
, все <h2>
на втором месте и т. д. И это потому, что именно такой порядок указан в массиве list_of_tags_you_want_to_scrape
.
это будет результат:
<article class = "container">
<h1>this is headline 1</h1>
<h2>this is headline 2</h2>
<p>this is paragraph</p>
</article>
поэтому я хотел бы, чтобы они были в правильном порядке, как в исходном HTML. После первого <h1>
должен быть элемент <p>
.
Это означает, что мне, вероятно, нужно будет сделать для каждого цикла также с inner_content
и проверить, равна ли каждая строка из этого inner_content хотя бы одному из тегов из массива. Если да, то сохраните, а затем перейдите на другую строку. Я попытался сделать это и сделал для каждого для inner_content получение строки за строкой, но это дало мне ошибку, и я не уверен, что это правильный способ, как это сделать. (Первый день использования модуля BeautifulSoup)
Любые советы или советы, как изменить мой метод для достижения этого, пожалуйста? Спасибо!
@JackFleeting Я сделал. Спасибо, что заметили меня.
@MapeSVK В вашем последнем примере порядок не должен быть [h1, h2, p]
, поскольку это структура самого HTML?
@ Ajax1234 Нет, не должно. Исходный порядок: h1 -> p -> h2. Это просто пример. Мне нужно придерживаться правильного порядка. Это не тема о том, как должен быть структурирован HTML. Это тема о правильном порядке после парсинга.
@MapeSVK Ввод всегда в виде <article>{something}</article>
?
@ Ajax1234 да, это так. И я только что попробовал ваше решение, и оно работает! Спасибо за ваши усилия!
@MapeSVK Нет проблем, рад помочь!
@ Ajax1234 Ajax1234, не могли бы вы очень кратко объяснить, как работает ваш метод «анализа»? Просто как бы вы прочитали метод.
@MapeSVK Пожалуйста, посмотрите мое недавнее редактирование.
Чтобы сохранить исходный порядок ввода html
, вы можете использовать рекурсию для перебора атрибута soup.contents
:
from bs4 import BeautifulSoup as soup
def parse(content, to_scrape = ['h1', 'h2', 'h3', 'p', 'li']):
if content.name in to_scrape:
yield content
for i in getattr(content, 'contents', []):
yield from parse(i)
Пример:
html = """
<html>
<body>
<h1>My website</h1>
<p>This is my first site</p>
<h2>See a listing of my interests below</h2>
<ul>
<li>programming</li>
<li>math</li>
<li>physics</li>
</ul>
<h3>Thanks for visiting!</h3>
</body>
</html>
"""
result = list(parse(soup(html, 'html.parser')))
Выход:
[<h1>My website</h1>, <p>This is my first site</p>, <h2>See a listing of my interests below</h2>, <li>programming</li>, <li>math</li>, <li>physics</li>, <h3>Thanks for visiting!</h3>]
Как видите, исходный порядок html сохраняется, и теперь его можно записать в файл:
with open('file.md', 'w') as f:
f.write('\n'.join(map(str, result)))
Каждый объект bs4
содержит, среди прочего, атрибуты name
и contents
. Атрибут name
— это само имя тега, а атрибут contents
хранит весь дочерний HTML. parse
использует генератор, чтобы сначала проверить, есть ли у переданного bs4
объекта тег, который принадлежит к списку to_scrape
, и если да, то yield
имеет это значение. Наконец, parse
перебирает содержимое content
и вызывает себя для каждого элемента.
Я просто изменил строку: result = list(parse(soup(html, 'html.parser'))) на result = list(parse(inner_content)) так как мой контент уже был после супа. Спасибо еще раз!
Две вещи: во-первых, как правило - если это вообще возможно, вы должны вставлять код, а не его изображения (в данном случае ваш вывод). Во-вторых, вы должны добавить к своему вопросу свой
inner_content
(или хотя бы репрезентативную часть), чтобы люди могли видеть, с чем вы имеете дело.