์ฝ๋ ๋ฆฌํฉํ ๋ง.
๋๋์ด.
์ ๋ฆฌ.
ํ๋ค
!!!!!!!
๋ฆฌํฉํ ๋ง ๊ณผ์ ์ด ๊ธธ๊ณ ๋ ํ๋ํ ๊ฑฐ ๊ฐ์ ์์ ์๋ฆฌ์ฆํํด์ ์ ๋ฆฌํ๋ฉด ๋์ค์ ์ฝ๋ ์ฌ๋ฏธ๊ฐ ์๊ฒ ๋ค ์ถ์ด์ ์ ๋ชฉ์ ๋ถ์ฌ๋ดค๋ค.
๋จ๋ค์ด ๋ณด๊ธฐ์ ์ด๋ค์ง ๋ชจ๋ฅด๊ฒ ์ง๋ง ์ผ๋จ ๋ ๋ง์์ ๋ ๋ค ๊ณ ๊ธ์ง ๋๋๋ ๋๊ณ ใ ใ ๐
์ฐ์ ์ด๋ค ๋ฉ์๋ ํ๋๋ฅผ ๋จผ์ ๊ณ ์ณ๋ณด๋ ค๊ณ ํ๋ค.
์ฌ์ค ์ด๊ฒ ์ค์๋๊ฐ ๋์ ๋ฆฌํฉํ ๋ง ์์ ์ ์๋์ง๋ง
์ด๋ฐ์ ํ๋ฃจ๋ฃฉ(?) ๋ง๋ค์ด๋ฒ๋ฆฐ ๋ฉ์๋๋ผ ์์ง ๋จ์ ์์ ๊ฐ๊ธฐ๋ ํ๊ณ ,,, ์ข ์นํด์ ธ์ผ๊ฒ ์ด์ ๋ง์ด๋ค ใ ใ
๋ฌธ์ ์
- soup = BeautifulSoup(html_content, 'html.parser')๋ฅผ ๋ฐ๋ณต ์์ฑํ๊ณ ์๋ค
- ํ๊ทธ ์ ๊ฑฐ/unwrap์ ์ฌ๋ฌ ๋ฒ ๋ฃจํํ๊ณ ์์
- a[href] ์ฒ๋ฆฌ์ ํ ์ด๋ธ ๋ง์ปค ์ถ๊ฐ๊ฐ ์์ฌ์์ด ๋ถ๋ฆฌํ๊ธฐ ์ด๋ ต๊ณ , ์ค๊ฐ์ table_str = table_str.replace ์ ๊ฐ์ ๋ฌธ์์ด ๊ต์ฒด ์ฝ๋๊ฐ ๋ถ์ฐ๋์ด ์์ด ๊ฒฐ๊ณผ์ ์ผ๋ก ๋ญ ํ๋ for๋ฌธ์ธ์ง ์ฝ๊ฒ ํ์ ํ๊ธฐ ์ด๋ ค์
๋ชฉํ
- BeautifulSoup ํ์ฑ 1ํ๋ก ๋๋ด์
- ๋ถํ์ํ ํ๊ทธ/์์ฑ ์ ๊ฑฐ๋จ์ ๋จ์ผ ํจ์๋ก ํตํฉํ์(๊ทธ๊ฑฐ๋ง ์์ฅ ๋ถ๋ฅด๊ฒ)
- ๋งํฌ ์นํ์ ํ ๋ฃจํ์์ ๋๋ด์(๋ฒ๊ฑฐ๋ก์ฐ๋๊น)
- ํ ์ด๋ธ ๋ถ๋ฆฌ์ body ์ ๋ฆฌ๋ ํ ํจ์์์ ๋ฐํํ๋๋ก ํ์
๊ต์ฒด ์ค๊ณ
- ์ ๊ท์ ์ฌ์ ์ปดํ์ผ: ๋ฃจํ๋ด ์ฌ์ปดํ์ผ ์ ๊ฑฐ
- ํ๊ทธ/์์ฑ ์ ๊ฑฐ ํ์คํ: 250820 ํ์ฌ ํฌ๊ฒ 2๊ฐ์ง๋ก ๋๋์ด์ ์ด๊ฑธ ๋ถ๋ฆฌํ๋ ค ํ๋ค -> ๊ณตํต(unwrap), FAQ ์ ์ฉ(unwrap) ๋ถ๋ฆฌ
- ํ
์ด๋ธ ๋ฃจํ 1ํ๋ก:
- <a>์์ linked_seq ์ถ์ถ -> [linkedN]์ผ๋ก ๊ต์ฒด
- [tableN] ๋ง์ปค๋ฅผ ์์ ๋ถ์ด๊ณ table.decompose()
- ๋ฐํ:
- str: ํ ์ด๋ธ์ด ์ ๊ฑฐ๋ body
- str: ๋ง์ปค+์๋ณธํ ์ด๋ธ HTML
- list: ๋งํฌ seq ๋ฆฌ์คํธ
๊ตฌํ ์์: HTML ์ ๋ฆฌ·ํ ์ด๋ธ/๋งํฌ ์ถ์ถ
ํฉ์ด์ ธ์๋ ๊ธฐ๋ฅ๋ค์ ๊ฒฐ์ ๋ง๊ฒ ๋ชจ์์ ํ๋์ ํจ์๋ก ์ผ์ํํ์.
BEFORE
def extract_and_clean_html_tables(html_string, is_faq=False):
soup = BeautifulSoup(html_string, 'html.parser')
# โ ๋ถํ์ ํ๊ทธ ์ ๊ฑฐ(์ฌ๋ฌ ๋ฒ ๋ฃจํ)
for tag in soup.find_all(['span','strong','hr','a','font','img']):
tag.unwrap()
if is_faq:
for tag in soup.find_all(['p','br','div']):
tag.unwrap()
# โก table ์์ฑ ์ ๊ฑฐ(์ฌ๋ฌ ๋ฒ ๋ฃจํ)
for t in soup.find_all(['table','tr','td','tbody']):
for attr in ['style','width','border','cellpadding','cellspacing']:
t.attrs.pop(attr, None)
tables_content = []
linked_seq_list = []
table_index = 1
# โข ํ
์ด๋ธ ์ํ ์ค๊ฐ์ a[href] ์ฒ๋ฆฌ + ๋ง์ปค ๋ฌธ์์ด ์กฐ์ + ์ ๊ฑฐ
for table in soup.find_all('table'):
for a_tag in table.find_all('a', href=True):
# href์์ /ํน์ URL/<์ซ์> ์ถ์ถ
parts = a_tag['href'].split('/ํน์ URL/')
if len(parts) > 1:
m = re.search(r'\d+', parts[1])
if m:
seq = m.group(0)
linked_seq_list.append(seq)
marker = f"[linked{len(linked_seq_list)}]"
# ๋ฌธ์์ด ์นํ/๋์ฒด
table_str = str(table)
table_str = table_str.replace(a_tag['href'], marker)
# ...
marker = f"[table{table_index}]"
tables_content.append(marker + "\n" + str(table) + "\n")
table.decompose()
table_index += 1
clean_html = str(soup)
return clean_html, tables_content, linked_seq_list
๋ชจ์ผ๊ธฐ
1. BeautifulSoup(html, 'html.parser') ๋ค์ค ํธ์ถ
2. ๋ถํ์ ํ๊ทธ ์ ๊ฑฐ ๋ฃจํ๋ค(span/strong/font/hr/a/img, FAQ ์ p/br/div)
3. <table> ๋ด๋ถ style/width/border/cellpadding/cellspacing ์์ฑ ์ ๊ฑฐ
4. <a href>์์ /linkPageDetailPop/<์ซ์> ์ถ์ถ → [linkedN] ์นํ
5. [tableN] ๋ง์ปค ์์ฑ + table.decompose() (๋ณธ๋ฌธ์์ ํ ์ด๋ธ ์ ๊ฑฐ)
6. ์ต์ข clean_html(๋ณธ๋ฌธ), tables_content(๋ง์ปค+ํ ์ด๋ธ HTML), linked_seq_list ๋ฐํ
[1] ๋ฃจํ ํตํฉ: ํ๊ทธ ์ ๊ฑฐ ๋ฃจํ
์ด 3๊ฐ์ง๋ฅผ ์ ๊ฑฐํจ
1. ๊ณตํต ์ ๊ฑฐ: span, strong, font, hr, a, img
2. FAQ ์ ์ฉ: p, br, div
3. ๋น ํ
์คํธ ๋
ธ๋ ์ ๊ฑฐ(๊ธฐ์กด์ ์ฐ๋ฐ์ ์ผ๋ก ์ฒ๋ฆฌ ๋๋ ๋ฏธ์ฒ๋ฆฌ): ์ด ๋ก์ง์ ์ด ๋ฉ์๋ ํธ์ถ ์ ์๋ ์ฐ์ด๋ ๋ก์ง์ผ๋ก ๋๊ฐ์ ์ฒ๋ฆฌ๋ฅผ ์ด ๋ฌ ๋ฒ ํด์ฃผ๊ณ ์์๋ค.. ์ด๊ฒ ๋ฐ๋ก ๋ฌด์ง์ฑ ๋ณต๋ถ์ ๊ฒฐ๊ณผ
def _unwrap_tags(soup: BeautifulSoup, is_faq: bool) -> None:
targets = set(UNWRAP_COMMON) | (UNWRAP_FAQ if is_faq else set())
# BeautifulSoup๋ setํ tag๋ช
๋ ํ์ฉ๋จ (์ถ์ฒ: chatgpt ใ
ใ
)
for tag in soup.find_all(targets):
tag.unwrap()
# ๋น ํ
์คํธ ๋
ธ๋ ์ ๊ฑฐ
for el in list(soup.find_all(string=True)):
if isinstance(el, NavigableString) and not el.strip():
el.extract()
์ฌ์ค ์ด๊ฑด ์งํผํฐํํ ์ง๋ฌ๋ผํ ์ฝ๋๋ค.
' ๊ณตํต ํ๊ทธ๋ฅผ ์์ํํด์ผ๊ฒ ๋ค'๊ณ ์๊ฐํ๋๋ฐ ์งํผํฐ๋ ๋ง์ฐฌ๊ฐ์ง์๋ ๋ณด๋ค ใ ใ
๋๋ ๋ฆฌ์คํธ๋ก ํ๊ทธ ๊ฒ์ํ๊ฒ ํ์๋๋ฐ, ์งํผํฐ๋ setํ์ ์ผ๋ก ๊ฒ์์ ํ๊ฒ๋ ์งฐ๋ค.
๋ญ ์ฌ์ค.. ์ด์ฐจํผ ๋ฌธ์์์ ๋ฑ์ฅํ๋ ์์๋๋ก ์ฒ๋ฆฌํ ๊ฑฐ๋๊น list๋ set๋๊ฐ ์ฌ๊ธฐ์ ์ค์ํ์ง ์๋ค..ใ ใ
[2] ๋ฉ์๋: ํ ์ด๋ธ ๋ด๋ถ ํ๊ทธ ์ฒ๋ฆฌ
def _clean_table_attrs(soup: BeautifulSoup) -> None:
for td in soup.find_all(["table","tr","td","tbody"]):
for attr in ("style","width","border","cellpadding","cellspacing"):
td.attrs.pop(attr, None)
[3] ๋ฉ์๋: ๋งํฌ ์ถ์ถ ๋ฐ ์นํ
def _extract_link_seq_and_replace(table: Tag, linked_seq_list: list[str]) -> None:
for a in table.find_all("a", href=True):
m = RE_LINK_SEQ.search(a.get("href") or "")
if not m:
continue
linked_seq_list.append(m.group(1))
a.replace_with(f"[linked{len(linked_seq_list)}]")
์ด๊ฑด ๊ฐ์ธ์ ์ผ๋ก ๋ง์ ๋๋ ๋ฉ์๋๋ค.
| BEFORE | AFTER |
| for table in soup.find_all('table'): for a_tag in table.find_all('a', href=True): # href์์ /linkPageDetailPop/<์ซ์> ์ถ์ถ parts = a_tag['href'].split('/linkPageDetailPop/') if len(parts) > 1: m = re.search(r'\d+', parts[1]) if m: seq = m.group(0) linked_seq_list.append(seq) marker = f"[linked{len(linked_seq_list)}] .. |
def _extract_link_seq_and_replace(table: Tag, linked_seq_list: list[str]) -> None: for a in table.find_all("a", href=True): m = RE_LINK_SEQ.search(a.get("href") or "") if not m: continue linked_seq_list.append(m.group(1)) a.replace_with(f"[linked{len(linked_seq_list)}]") |
split ๋์ ์ ๊ท์ ๊ฒ์์ ์ผ๋ค.
๊ตณ์ด ์๋ผ๋ผ ํ์ ์์ด re.search๋ href ์ ์ฒด ๋ฌธ์์ด์ ์ค์บํด์ฃผ๋๊น ํด๋น URL๋ค์ ์ค๋ ์ซ์ seq๋ฅผ ๋ฐ๋ก ์ก์์ ๋ฆฌ์คํธ์ ์ ์ฅ + ์นํํ๋๋ฐ ๋ฌธ์ ์์ด ์๋ํ๋ค.
์ด ์ฝ๋๋ฅผ ์งํผํฐํํ ๋ณด์ฌ์ฃผ๋๊น ์งํผํฐ๋ ์ด๋ ๊ฒ ๋งํ๋๋ผ:
_extract_link_seq_and_replace(table, linked_seq_list)๊ธฐ์กด ์์น(์คํฌ๋ฆฐ์ท ๋งค์นญ)
|
DOM ์์ค ์นํ? ์ ๊ท์ '์ฌ์ ์ปดํ์ผ'?
์ข ๋ ์ฐพ์๋ด์ผ๊ฒ ๋๋ฐ..? ์ด๊ฑด ๋ค์ ํฌ์คํธ์..