Skip to content

Latest commit

Β 

History

History
537 lines (417 loc) Β· 23.8 KB

CONCEPT.md

File metadata and controls

537 lines (417 loc) Β· 23.8 KB

μ»€μŠ€ν…€ 크둀러 컨셉

μ»€μŠ€ν…€ ν¬λ‘€λŸ¬λŠ” 크둀링 및 정보 가곡 과정을 μžλ™ν™”ν•˜κ±°λ‚˜, ν”„λ‘œκ·Έλž˜λ¨Έκ°€ μ’€ 더 μ‰½κ²Œ 크둀러λ₯Ό 생성할 수 μžˆλ„λ‘ 도와쀄 수 μžˆλŠ” ν”„λ‘œκ·Έλž¨μž…λ‹ˆλ‹€.

이 λ‚΄μš©μ€ μ»€μŠ€ν…€ 크둀러의 컨셉이며, 섀계 쀑 μ–Έμ œλ“ μ§€ λ°”λ€” 수 μžˆμŠ΅λ‹ˆλ‹€.

0. HTML 뢄석 방법에 λŒ€ν•΄

HTML은 μ›Ή 브라우져의 μš”μ²­μœΌλ‘œ μ„œλ²„μ—μ„œ λ‹€μš΄λ‘œλ“œλ˜λŠ” νŒŒμΌμž…λ‹ˆλ‹€. 이 νŒŒμΌμ—λŠ” .js와 같은 λ©”νƒ€νŒŒμΌ 정보가 ν¬ν•¨λ˜λ©°, μ›Ή λΈŒλΌμš°μ ΈλŠ” λ©”νƒ€νŒŒμΌμ„ λͺ¨λ‘ λΉ„λ™κΈ°λ‘œ λ‹€μš΄λ‘œλ“œν•˜μ—¬ μžλ°”μŠ€ν¬λ¦½νŠΈ μ—”μ§„μœΌλ‘œ, λ˜λŠ” 기타 μ›Ή λΈŒλΌμš°μ €μ—μ„œ μ œκ³΅ν•˜λŠ” κΈ°λŠ₯λ“€λ‘œ μ‹€ν–‰ν•©λ‹ˆλ‹€. μ›Ή λΈŒλΌμš°μ ΈλŠ” μ›Ή νŽ˜μ΄μ§€λ₯Ό λ‘œλ”©ν•  λ•Œ 크게 λ‹€μŒκ³Ό 같은 과정을 κ±°μΉœλ‹€κ³  ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

HTML λ‹€μš΄λ‘œλ“œ -> HTML 뢄석 -> META(.js, .css λ“±λ“±) 데이터 μš”μ²­ -> λ Œλ”λ§ μ‹œμž‘ 및 Javascript, WASM, PHP λ“± μ½”λ“œ μ‹€ν–‰

0.1. HTML 뢄석 단계

HTML 뢄석 λ‹¨κ³„μ—μ„œ μ›Ή λΈŒλΌμš°μ ΈλŠ” HTML을 νŒŒμ‹±ν•˜κ³  κ΅μ •ν•˜λŠ” 과정을 κ±°μΉ©λ‹ˆλ‹€. ꡐ정과정은 HTML 파일이 HTML 문법에 λ§žμ§€ μ•ŠλŠ” 경우 이λ₯Ό μžλ™μœΌλ‘œ μˆ˜μ •ν•΄μ£ΌλŠ” κ³Όμ •μž…λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄ <table> νƒœκ·Έ ν•˜μœ„μ— <tbody>κ°€ μ˜€μ§€μ•Šκ³  λ°”λ‘œ <tr> νƒœκ·Έκ°€ λ‚˜μ˜¨λ‹€λ©΄ HTML λΆ„μ„λ‹¨κ³„μ—μ„œ <tbody>νƒœκ·Έλ₯Ό μΆ”κ°€ν•¨μœΌλ‘œμ¨ HTML을 κ΅μ •ν•©λ‹ˆλ‹€.

λ”°λΌμ„œ μ›Ή 브라우져의 κ°œλ°œμžλ„κ΅¬μ—μ„œ ν‘œμ‹œλ˜λŠ” HTML은 URL둜 λ‹€μš΄λ‘œλ“œν•œ HTMLκ³Ό μƒλ‹Ήνžˆ λ‹€λ₯Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

0.2. XPath?

XPathλŠ” HTML의 νŠΉμ • λ…Έλ“œλ₯Ό ν•˜λ‚˜μ˜ μ‹μœΌλ‘œ λ‚˜νƒ€λ‚Έ κ²ƒμž…λ‹ˆλ‹€. 이 식은 μ—¬λŸ¬κ°œμ˜ λ…Έλ“œλ₯Ό 가리킬 수 있고, ν•˜λ‚˜μ˜ λ…Έλ“œλ§Œ 가리킬 수 μžˆμŠ΅λ‹ˆλ‹€. 크둀러λ₯Ό λ§Œλ“€ λ•Œ 주둜 XPath의 λͺ¨λ“  μŠ€νŽ™λ“€μ„ 두루 ν™œμš©ν•˜μ§€λ§Œ, HTML을 λΆ„μ„ν•΄μ£ΌλŠ” λΌμ΄λΈŒλŸ¬λ¦¬λ“€ 덕뢄이 ꡳ이 λͺ¨λ“  μŠ€νŽ™λ“€μ„ μ‚¬μš©ν•˜μ§€ μ•Šκ³  νŠΉμ • ν•˜λ‚˜μ˜ λ…Έλ“œ μœ„μΉ˜λ₯Ό μ•Œ 수 μžˆλŠ” XPathλ₯Ό μ‰½κ²Œ 얻을 수 μžˆμŠ΅λ‹ˆλ‹€.

크둬 개발자 λ„κ΅¬μ˜ Inspector둜 νŠΉμ • Elementsλ₯Ό μ„ νƒν•˜κ³ , νŠΉμ • HTML λ…Έλ“œμ— 마우슀 였λ₯Έμͺ½ 클릭 ν›„ Copy -> Copy full XPathλ₯Ό λˆ„λ₯΄λ©΄ νŠΉμ • ν•œ λ…Έλ“œμ˜ μœ„μΉ˜λ₯Ό μ•Œ 수 μžˆλŠ” XPathκ°€ λ³΅μ‚¬λ©λ‹ˆλ‹€.

/html/body/div[3]/div[1]
/html[1]/body[1]/div[3]/div[1]

μœ„μ™€ 같은 μ–΄λ–€ 식이 λ³΅μ‚¬λ˜λŠ”λ° 이 식이 λ°”λ‘œ XPathμž…λ‹ˆλ‹€. 두 식은 같은 ν‘œκΈ°μ΄λ©°, μ•„λž˜μ‹μ„ 주둜 μ‚¬μš©ν•  κ²λ‹ˆλ‹€. HTML은 트리ꡬ쑰이며 트리의 ν•˜μœ„ λ…Έλ“œλ“€μ— μˆœμ„œκ°€ 있기 λ•Œλ¬Έμ— μœ„μ™€ 같은 ν‘œκΈ°κ°€ κ°€λŠ₯ν•©λ‹ˆλ‹€.

μœ„ 식은 <html> νƒœκ·Έλ₯Ό 가진 첫 번째 ν•˜μœ„ λ…Έλ“œλ₯Ό μ„ νƒν•˜κ³ , κ·Έ ν•˜μœ„ λ…Έλ“œμ˜ 첫 번째 <body> λ…Έλ“œλ₯Ό μ„ νƒν•˜κ³ , 이제 μ„Έ 번째둜 λ‚˜μ˜€λŠ” <div> νƒœκ·Έλ₯Ό μ„ νƒν•˜κ³ , λ§ˆμ§€λ§‰μœΌλ‘œ 첫 번째둜 λ‚˜μ˜€λŠ” <div>λ₯Ό μ„ νƒν•˜λΌλŠ” 의미 μž…λ‹ˆλ‹€.

μ„Έ 번째둜 λ‚˜μ˜€λŠ” <div>νƒœκ·ΈλŠ” <body>의 λͺ¨λ“  ν•˜μœ„ λ…Έλ“œλ“€ 쀑에 div νƒœκ·Έλ₯Ό κ°€μ§€λŠ” λͺ¨λ“  νƒœκ·Έλ“€μ„ μˆœμ„œλŒ€λ‘œ λ‚˜μ—΄ν–ˆμ„ λ•Œ μ„Έ 번째 λ‚˜μ˜€λŠ” divλ₯Ό μ„ νƒν•˜λΌλŠ” μ˜λ―Έμž…λ‹ˆλ‹€.

1. 크둀링 방법둠

ν¬λ‘€λ§μ—λŠ” 크게 두 가지 방법이 μžˆμŠ΅λ‹ˆλ‹€. 정적 뢄석과 동적 λΆ„μ„μž…λ‹ˆλ‹€. 정적 뢄석은 URL을 톡해 λ°”λ‘œ λ‹€μš΄λ‘œλ“œλ°›μ€ HTMLνŒŒμΌμ„ λ°”λ‘œ λΆ„μ„ν•˜λŠ” 방법이며, 동적 뢄석은 μ›Ή λΈŒλΌμš°μ Έκ°€ 메타데이터 및 μžλ°”μŠ€ν¬λ¦½νŠΈ μ½”λ“œ 등에 μ˜ν•΄ λΉ„λ™κΈ°μ μœΌλ‘œ μΆ”κ°€λ˜λŠ” μ›Ή μš”μ†Œλ“€μ„ λΆ„μ„ν•˜λŠ” λ°©λ²•μž…λ‹ˆλ‹€.

2. 정적 뢄석

정적 뢄석은 BeautifulSoup4λ‚˜ HtmlAgilityPackκ³Ό 같은 HTML νŒŒμ‹± 도ꡬλ₯Ό μ‚¬μš©ν•˜λ©΄ μ‰½κ²Œ κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

2.1. HTML을 트리 λ…Έλ“œλ‘œ λ‚˜νƒ€λ‚΄κΈ°

HTML은 기본적으둜 #document λ…Έλ“œλ₯Ό λ£¨νŠΈλ…Έλ“œλ‘œν•˜λŠ” 트리ꡬ쑰이며, λ”°λΌμ„œ 트리 자료ꡬ쑰둜 μ‰½κ²Œ λ‚˜νƒ€λ‚Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

540

λŒ€μΆ© μ΄λ ‡κ²Œ

정적 뢄석에선 HTML이 트리 ꡬ쑰인 것을 적극적으둜 ν™œμš©ν•©λ‹ˆλ‹€.

2.2. λ…Έλ“œλ“€μ˜ 곡톡 λΆ€λΆ„ μ°ΎκΈ° - LCA

<table> νƒœκ·Έμ˜ ν•˜μœ„ λ…Έλ“œλ“€μ€ <tr>λ“€λ‘œ κ΅¬μ„±λ˜μ–΄μžˆμœΌλ©°, <tr>듀은 μ„ ν˜•μœΌλ‘œ λ‚˜νƒ€λ‚©λ‹ˆλ‹€.

예λ₯Όλ“€μ–΄ λ‹€μŒκ³Ό 같은 κ²Œμ‹œνŒμ„ λΆ„μ„ν•΄λ΄…μ‹œλ‹€.

μ‚¬μ§„μ˜ μš°μΈ‘μ—μ„œ λ³Ό 수 μžˆλ‹€μ‹œν”Ό <table>λ…Έλ“œλŠ” <caption>, <tbody> λ“±μ˜ ν•˜μœ„λ…Έλ“œλ₯Ό 가지며 <tbody>λŠ” <tr>듀을 λ…Έλ“œλ‘œ κ°€μ§‘λ‹ˆλ‹€. μ—¬κΈ°μ„œ <tr>νƒœκ·Έλ“€μ΄ μ„ ν˜•μ μœΌλ‘œ λ‚˜νƒ€λ‚¨μ„ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€. 그러면 XPath둜 λ‹€μŒκ³Ό 같이 λ‚˜νƒ€λ‚Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

.../table[1]/tbody[1]/tr[1]
.../table[1]/tbody[1]/tr[2]
.../table[1]/tbody[1]/tr[3]
...

μ—¬κΈ°μ„œ .../table[1]/tbody[1] 뢀뢄은 λͺ¨λ“  ν•˜μœ„ λ…Έλ“œμ—μ„œ κ³΅ν†΅μ μœΌλ‘œ λ‚˜νƒ€λ‚˜λŠ” λΆ€λΆ„μž…λ‹ˆλ‹€. 이 뢀뢄듀을 μ œκ±°ν•˜λ©΄ <tr> λ…Έλ“œλ“€μ΄ μ„ ν˜•μ μœΌλ‘œ λ‚˜μ—΄λ˜μ–΄μžˆλŠ” 뢀뢄이 λ³΄μž…λ‹ˆλ‹€. 이제 λͺ¨λ“  <tr> λ…Έλ“œλ“€μ„ λ°©λ¬Έν•˜κΈ° μœ„ν•΄ λ‹€μŒκ³Ό 같은 λ°˜λ³΅λ¬Έμ„ μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

HtmlTree tree = HtmlTree.Parse(html)
string parent = '../table[1]/tbody[1]'

for ( int i = 0; ; i++ )
{
  string child_xpath = parent + '/tr[' + (i+1) + ']'
  Node child_node = tree.select_xpath(child)

  if (child_node == nullptr) break;

  // ...
}

κ°„λ‹¨ν•˜κ²Œ 식 ν•˜λ‚˜λ‘œλ„ ν‘œμ‹œν•  수 μžˆμŠ΅λ‹ˆλ‹€.

.../table[1]/tbody[1]/tr[{i+1}]

μœ„ μ½”λ“œλ₯Ό μ΄μš©ν•΄ μžμ‹ λ…Έλ“œλ₯Ό μ‰½κ²Œ λ°©λ¬Έν•  수 있고, λͺ¨λ“  μžμ‹ λ…Έλ“œλ“€μ΄ 같은 ν˜•μ‹μ„ 가진닀면 ν•˜λ‚˜μ˜ λ£¨ν‹΄μœΌλ‘œ λͺ¨λ“  μžμ‹μ„ μ‰½κ²Œ 뢄석할 수 μžˆμŠ΅λ‹ˆλ‹€.

μ‹€μ œλ‘œ μœ„ νŽ˜μ΄μ§€(λ””μ‹œ νž›κ°€ κ²Œμ‹œνŒ λͺ©λ‘)을 C#으둜 κ΅¬ν˜„ν•œ μ½”λ“œλŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

public class Pattern
{
    public string Number;
    public string Icon;
    public string Title;
    public string Author;
    public string WriteTime;
    public string Views;
    public string UpVote;
}

public Pattern Extract(string html)
{
    HtmlDocument document = new HtmlDocument();
    document.LoadHtml(html);
    var result = new Pattern();
    var root_node = document.DocumentNode;
    for (int i = 1; ; i++)
    {
        var node = root_node.SelectSingleNode($"/html[1]/body[1]/div[2]/div[2]/main[1]/section[1]/article[2]/div[2]/table[1]/tbody[1]/tr[{3+i*1}]");
        if (node == null) break;
        result.Number = node.SelectSingleNode("./td[1]").InnerText;
        result.Icon = node.SelectSingleNode("./td[2]/a[1]/em[1]").InnerText;
        result.Title = node.SelectSingleNode("./td[2]/a[1]").InnerText;
        result.Author = node.SelectSingleNode("./td[3]").InnerText;
        result.WriteTime = node.SelectSingleNode("./td[4]").InnerText;
        result.Views = node.SelectSingleNode("./td[5]").InnerText;
        result.UpVote = node.SelectSingleNode("./td[6]").InnerText;

    }
}

μ΄λ ‡κ²Œ λ…Έλ“œλ“€μ˜ 곡톡 λΆ€λΆ„ (μ΅œμ†Œ 곡톡 쑰상 - LCA)을 μ°ΎλŠ” 방법은 μ•žμœΌλ‘œ 크둀링 λ°˜μžλ™ν™”μ„ ν¬ν•¨ν•œ λͺ¨λ“  λΆ€λΆ„μ—μ„œ 주둜 μ‚¬μš©λ©λ‹ˆλ‹€. μœ„ μ½”λ“œλŠ” λ°˜μžλ™ν™” κΈ°λŠ₯으둜 μžλ™μœΌλ‘œ κ΅¬ν˜„λœ μ½”λ“œμž…λ‹ˆλ‹€.

2.3. ν΄λŸ¬μŠ€ν„°λ§

ν΄λŸ¬μŠ€ν„°λ§μ€ λΉ„μŠ·ν•œ 뢀뢄듀을 λ¬ΆλŠ” 것을 μ˜λ―Έν•©λ‹ˆλ‹€. μ›Ή νŽ˜μ΄μ§€μ—μ„œ λΉ„μŠ·ν•œ 뢀뢄듀을 λ¬ΆλŠ” 것을 예둜 λ“€μ–΄λ΄…μ‹œλ‹€.

μœ„μ™€ 같이 λΉ„μŠ·ν•œ 뢀뢄듀을 μžλ™μœΌλ‘œ μ°Ύμ•„λ‚΄κ³  정보듀을 μΆ”μΆœν•˜λŠ” 것을 μ£Ό λͺ©μ μœΌλ‘œ, μ—¬λŸ¬ ν΄λŸ¬μŠ€ν„°λ§ 방법듀 λ§Œλ“€μ–΄ μ‹œλ„ν–ˆμŠ΅λ‹ˆλ‹€.

2.3.1. μ„ ν˜• ν΄λŸ¬μŠ€ν„°λ§

μ„ ν˜• ν΄λŸ¬μŠ€νŒ…μ€ μ„ ν˜•μœΌλ‘œ λ‚˜μ—΄λœ μš”μ†Œλ“€μ„ ν΄λŸ¬μŠ€ν„°λ§ν•˜λŠ” λ°©λ²•μž…λ‹ˆλ‹€. 2.3의 사진을 보면 κ²Œμ‹œλ¬Ό λͺ©λ‘, μ΄λ‚˜ 졜근 λ°©λ¬Έ κ°€λŸ¬λ””λ“€, ν₯ν•œ κΈ€ μˆœμœ„, 가러리 λ¦¬μŠ€νŠΈλ“€μ΄ μˆœμ„œλŒ€λ‘œ λ‚˜μ—΄λ˜μ–΄μžˆλŠ” κ±Έ λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€. 이런 λ¦¬μŠ€νŠΈλ“€μ΄ μ„ ν˜• ν΄λŸ¬μŠ€ν„°λ§μ˜ λŒ€μƒμ΄ λ©λ‹ˆλ‹€. 이 방법을 톡해 λŒ€λΆ€λΆ„μ˜ μ›Ή μš”μ†Œλ“€μ„ 뢄석할 수 μžˆλ‹€κ³  κΈ°λŒ€ν•©λ‹ˆλ‹€.

μ„ ν˜• ν΄λŸ¬μŠ€ν„°λ§μ˜ 기본적인 μ•„μ΄λ””μ–΄λŠ” μ–΄λ–€ νŠΉμ •λ…Έλ“œμ˜ ν•˜μœ„λ…Έλ“œλ“€μ˜ μœ μ‚¬λ„λ₯Ό λΉ„κ΅ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. ν•˜μœ„ λ…Έλ“œλ“€μ΄ 많고 정확도가 λ†’λ‹€λ©΄ μ„ ν˜•μœΌλ‘œ λ‚˜μ—΄λœ 뢀뢄이 κ²€μƒ‰λ˜μ—ˆμ„ ν™•λ₯ μ΄ 맀우 λ†’μŠ΅λ‹ˆλ‹€. λ‹€μŒ μ˜ˆμ œλŠ” μ„ ν˜• ν΄λŸ¬μŠ€ν„°λ§μ„ 톡해 μ„ ν˜•μ  μš”μ†Œλ₯Ό 찾은 κ²°κ³Όμž…λ‹ˆλ‹€.

μœ„ μ‚¬μ§„μ—μ„œ μ’ŒμΈ‘μ€ μ„ ν˜• 클러슀트링의 결과이고, μš°μΈ‘μ€ κ°€μž₯ λ§Žμ€ μš”μ†Œλ“€κ³Ό 정확도가 높은 κ²°κ³Όλ₯Ό ν•˜λ‚˜ μ„ νƒν•œ κ²°κ³Όλ₯Ό ꡡ은 λ…Έλž€μƒ‰ λ°•μŠ€λ‘œ λ‚˜νƒ€λ‚Έ κ²°κ³Όμž…λ‹ˆλ‹€. Accuracyκ°€ 1둜 λͺ¨λ“  ν•˜μœ„λ…Έλ“œλ“€μ΄ 같은 ν˜•νƒœλ₯Ό 가지고 있으며, 이런 ν•˜μœ„λ…Έλ“œλ“€μ΄ 51κ°œλ‚˜μžˆλŠ” λͺ¨μŠ΅μ„ λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒμ€ 이 κΈ°λŠ₯을 κ΅¬ν˜„ν•œ μ½”λ“œμž…λ‹ˆλ‹€.

public List<(int, double, HtmlNode, List<HtmlNode>)> LinearClustering(int min_child_count = 2, double min_diff_rate = 0.6)
{
    var result = new List<(int, double, HtmlNode, List<HtmlNode>)>();
    // λͺ¨λ“  λ…Έλ“œλ“€ 순회
    // foreach μ΄μ€‘λ£¨ν”„λŠ” λͺ¨λ“  λ…Έλ“œλ₯Ό μˆœνšŒν•¨.
    foreach (var nn in depth_map)
        foreach (var node in nn)
        {
            if (node.ChildNodes.Count >= min_child_count)
            {
                var ff = filtering_child_node(node);
                var h2 = get_child_node_hashs_2nd(ff);

                if (h2.Count == 0)
                    continue;

                var diff = estimate_diff_node_hashs(h2);

                if (diff >= min_diff_rate)
                    result.Add((ff.Count, diff, node, ff));
            }
        }

    return result;
}

private string get_child_node_hash(HtmlNode node)
{
    return node.Name + "/" + string.Join("/", node.ChildNodes.Select(x => x.Name));
}

// μ“Έλͺ¨μ—†λŠ” ν•˜μœ„ λ…Έλ“œλ“€μ„ μ‚­μ œν•¨
private List<HtmlNode> filtering_child_node(HtmlNode node)
{
    var childs = node.ChildNodes.ToList();
    // μ£Όμ„μ΄λ‚˜ 슀크립트 ν…μŠ€νŠΈκ°™μ΄ HTML ꡬ쑰에 큰 ꡬ쑰에
    // 큰 영ν–₯을 λ―ΈμΉ˜λŠ” λ…Έλ“œκ°€ μ•„λ‹ˆλ©΄ λͺ¨λ‘ 비ꡐ λŒ€μƒμ—μ„œ μ‚­μ œν•¨
    childs.RemoveAll(x => x.Name == "#comment";
    childs.RemoveAll(x => x.Name == "script");
    childs.RemoveAll(x => x.Name == "#text");
    childs.RemoveAll(x => x.Name == "meta");
    childs.RemoveAll(x => x.Name == "link");
    childs.RemoveAll(x => x.Name == "title");
    childs.RemoveAll(x => x.Name == "head");
    childs.RemoveAll(x => x.Name == "style");
    if (node.Name == "tbody" || node.Name == "table")
        return childs.Where(x => x.Name == "tr").ToList();
    return childs;
}

private List<string> get_child_node_hashs_2nd(List<HtmlNode> child_nodes)
{
    return child_nodes.Select(x => get_child_node_hash(x)).ToList();
}

// hashs의 μš”μ†Œ 쀑 κ°€μž₯ λ§Žμ€ 곡톡 λ…Έλ“œλ“€μ˜ 개수λ₯Ό hashs의 μš”μ†Œ 개수둜 λ‚˜λˆˆκ°’
private double estimate_diff_node_hashs(List<string> hashs)
{
    var hash = new Dictionary<string, int>();

    hashs.ForEach(x =>
    {
        if (!hash.ContainsKey(x))
            hash.Add(x, 0);
        hash[x] += 1;
    });

    return hash.Select(x => x.Value).Max() / (double)hashs.Count;
}

2.3.2. μŠ€νƒ€μΌ ν΄λŸ¬μŠ€ν„°λ§ (κ°€μ‹œμ„± 기반 뢄석)

μ›Ή λ””μžμ΄λ„ˆ(ν”„λ‘ νŠΈμ—”λ“œ 개발자)λŠ” μ΄μš©μžλ“€μ„ μœ„ν•œ UI/UXλ₯Ό 섀계λ₯Ό ν–ˆμ„ κ°€λŠ₯성이 맀우 λ†’μŠ΅λ‹ˆλ‹€. μ ‘κ·Όν•˜κΈ° μ‰¬μš΄ λΆ€λΆ„(맨 μ²˜μŒμ— λ³΄μ΄λŠ” λΆ€λΆ„)μ—” κ°€μž₯ μ€‘μš”ν•œ 정보와 μ•žμœΌλ‘œ μ„€λͺ…ν•  λ‚΄μš©λ“€μ„ μš”μ•½ν•΄ ν‘œμ‹œλ˜μ–΄μžˆμ„ ν™•λ₯ μ΄ 맀우 λ†’μœΌλ©° νŽ˜μ΄μ§€ ν•˜λ‹¨μ—λŠ” μ›Ή νŽ˜μ΄μ§€μ˜ μ˜λ„λ₯Ό μ •ν™•ν•˜κ²Œ μ „λ‹¬ν•˜κΈ° μœ„ν•œ 정보듀을 μ œκ³΅ν–ˆμ„ κ°€λŠ₯성이 λ†’μŠ΅λ‹ˆλ‹€. μ›Ή νŽ˜μ΄μ§€μ—μ„œ κ°€μž₯ λ§Žμ€ 뢀뢄을 μ°¨μ§€ν•˜λŠ” 뢀뢄이 κ°€μž₯ 핡심이 λ˜λŠ” 뢀뢄이며, λ”°λΌμ„œ 이 뢀뢄이 κ°€μž₯ μ€‘μš”ν•œ 뢀뢄일 κ°€λŠ₯성이 제일 λ†’μŠ΅λ‹ˆλ‹€.

μŠ€νƒ€μΌ 정보λ₯Ό μ–»μ–΄μ˜€λŠ” 것은 μ›Ή 브라우져의 도움이 ν•„μš”ν•©λ‹ˆλ‹€. μ›Ή λΈŒλΌμš°μ Έμ—μ„œ μ–΄λ–€ μ›μ†Œκ°€ μ–΄λŠ μ •λ„μ˜ 크기λ₯Ό κ°€μ§€λŠ” 지에 λŒ€ν•œ 정보λ₯Ό λͺ¨λ‘ κ°€μ Έμ˜€κ³ , 이제 μ–΄λ–€ λ…Έλ“œκ°€ μ°¨μ§€ν•˜λŠ” μ˜μ—­μ˜ 넓이λ₯Ό, κ·Έ λ…Έλ“œμ˜ ν•˜μœ„ λ…Έλ“œλ“€μ΄ μ°¨μ§€ν•˜λŠ” μ˜μ—­μ˜ 넓이λ₯Ό ν•©μΉœ κ°’μœΌλ‘œ λ‚˜λˆˆ κ°’μ˜ μ—­μˆ˜λ₯Ό κ΅¬ν•˜μ—¬ μ–΄λ–€ μ˜μ—­μ˜ μ „μš©λ©΄μ μ„ κ΅¬ν•©λ‹ˆλ‹€.

μ „μš©λ©΄μ μ΄ μƒμœ„ λ…Έλ“œμ˜ 넓이와 λΉ„μŠ·ν•˜λ©΄μ„œ νŽ˜μ΄μ§€μ—μ„œ μ°¨μ§€ν•˜λŠ” 넓이가 κ°€μž₯ λ„“κ±°λ‚˜, ν•˜μœ„ λ…Έλ“œμ˜ κ°œμˆ˜κ°€ λ§Žμ„ 수둝 μ‚¬μš©μžμ—κ²Œ μ€‘μš”ν•˜λ‹€κ³  인식될 κ°€λŠ₯성이 λ†’μŠ΅λ‹ˆλ‹€.

2.4. νŒ¨ν„΄ 뢄석

νŒ¨ν„΄ 뢄석 방법은 μ–΄λ–€ λ…Έλ“œμ™€ κ·Έ ν•˜μœ„ λ…Έλ“œλ“€μ΄ λͺ¨λ‘ ν¬ν•¨λœ μƒνƒœμ˜ ꡬ쑰가 νŽ˜μ΄μ§€μ˜ λ‹€λ₯Έ κ³³μ—μ„œ λ‚˜νƒ€λ‚˜λŠ”μ§€, λ˜ν•œ μ–΄λŠμ •λ„μ˜ μœ μ‚¬λ„λ₯Ό κ°€μ§€λ©΄μ„œ λ‚˜νƒ€λ‚˜λŠ”μ§€λ₯Ό λΆ„μ„ν•˜λŠ” λ°©λ²•μž…λ‹ˆλ‹€. νŒ¨ν„΄ 뢄석을 톡해 μš”μ†Œ νƒœκΉ… 생산성을 κ·ΉλŒ€ν™”μ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.

2.4.1. μ„ ν˜• νŒ¨ν„΄ 비ꡐ

HTML λ…Έλ“œλ₯Ό μ„ ν˜•ν™” μ‹œν‚€κΈ° μœ„ν•œ λ°©λ²•μœΌλ‘œ μ—¬λŸ¬λ°©λ²•μ΄ μžˆκ² μ§€λ§Œ, 이번 νŒ¨ν„΄ 뢄석 방법에선 ν›„μœ„νƒμƒ‰μ„ μ΄μš©ν•΄ μœ μΌν•œ νŒ¨ν„΄ λ¬Έμžμ—΄μ„ μƒμ„±ν•©λ‹ˆλ‹€. make_string ν•¨μˆ˜λŠ” νŠΉμ • λ…Έλ“œλ₯Ό μ„ ν˜•ν™”ν•©λ‹ˆλ‹€.

// for DP
Dictionary<HtmlNode, string> msdp = new Dictionary<HtmlNode, string>();
private string make_string(HtmlNode node)
{
    if (node.ChildNodes.Count == 0)
    {
        if (node.Name == "#text")
            return "#";
        return $"({node.Name})";
    }
    if (msdp.ContainsKey(node))
        return msdp[node];
    var ms = $"({node.Name}{string.Join("", node.ChildNodes.ToList().Where(x => x.Name != "#comment").Select(x => make_string(x)))})";
    msdp.Add(node, ms);
    return ms;
}

μœ„λŠ” μ™Όμͺ½μ˜ λ…ΈνŠΈ νŠΈλ¦¬κ°€ 우츑의 νŒ¨ν„΄ λ¬Έμžμ—΄λ‘œ λ³€ν™˜λ˜λŠ” μ˜ˆμ œμž…λ‹ˆλ‹€. #은 λ¬Έμžμ—΄(#text)을 κ°€λ¦¬ν‚΅λ‹ˆλ‹€.

두 νŒ¨ν„΄ λ¬Έμžμ—΄μ΄ μ–΄λŠ μ •λ„μ˜ μœ μ‚¬λ„λ₯Ό κ°€μ§€λŠ” 지λ₯Ό κ³„μ‚°ν•˜κΈ° μœ„ν•΄, 두 개의 λ¬Έμžμ—΄μ˜ μžμΉ΄λ“œ μœ μ‚¬λ„λ₯Ό κ΅¬ν•©λ‹ˆλ‹€. λ ˆλ²€μŠˆνƒ€μΈ μ•Œκ³ λ¦¬μ¦˜μ„ 톡해 두 λ¬Έμžμ—΄μ˜ μ„ ν˜•μ μΈ 차이λ₯Ό 계산할 수 있으며, 이 차이λ₯Ό κΈ΄ λ¬Έμžμ—΄μ˜ 길이둜 λ‚˜λˆ”μœΌλ‘œμ¨ μœ μ‚¬λ„λ₯Ό ꡬ할 수 μžˆμŠ΅λ‹ˆλ‹€.

public static int ComputeLevenshteinDistance(this string a, string b)
{
    int x = a.Length;
    int y = b.Length;
    int i, j;

    if (x == 0) return x;
    if (y == 0) return y;
    int[] v0 = new int[(y + 1) << 1];

    for (i = 0; i < y + 1; i++) v0[i] = i;
    for (i = 0; i < x; i++)
    {
        v0[y + 1] = i + 1;
        for (j = 0; j < y; j++)
            v0[y + j + 2] = Math.Min(Math.Min(v0[y + j + 1], v0[j + 1]) + 1, v0[j] + ((a[i] == b[j]) ? 0 : 1));
        for (j = 0; j < y + 1; j++) v0[j] = v0[y + j + 1];
    }
    return v0[y + y + 1];
}

λ‹€μŒμ€ νŒ¨ν„΄ 비ꡐ μ˜ˆμ œμž…λ‹ˆλ‹€.

비ꡐ λŒ€μƒ νŒ¨ν„΄: (tr#(td#)#(td#(a(em)#)#(a(span#))#)#(td#(span(em#))(a(img))#)#(td#)#(td#)#(td#)#)

정확도    νŒ¨ν„΄ λ‚΄μš©
( 64.2%) (tr#(td#)#(td(a(em)(b#)))#(td(b#))#(td#)#(td#)#(td#)#)
( 75.3%) (tr#(td#)#(td#(a(em)(b(b#)))#(a(span#))#)#(td#(b(b#))#)#(td#)#(td#)#(td#)#)
( 71.6%) (tr#(td#)#(td#(a(em)(b(b(font#))))#(a(span#))#)#(td#(b(b#))#)#(td#)#(td#)#(td#)#)
( 92.6%) (tr#(td#)#(td#(a(em)#)#(a(span#))#)#(td#(span(em#))(span#)#)#(td#)#(td#)#(td#)#)
(100.0%) (tr#(td#)#(td#(a(em)#)#(a(span#))#)#(td#(span(em#))(a(img))#)#(td#)#(td#)#(td#)#)

2.4.2. νŒ¨ν„΄ 맀칭

μ•žμ„œ μ–΄λ–€ λ…Έλ“œλ₯Ό μ„ ν˜•μœΌλ‘œ λ‚˜νƒ€λ‚΄μ–΄ λ‹€λ₯Έ λ…Έλ“œμ™€μ˜ μœ μ‚¬λ„λ₯Ό λΉ„κ΅ν•˜λŠ” 예제λ₯Ό μ„€λͺ…ν–ˆμŠ΅λ‹ˆλ‹€. νŒ¨ν„΄ 맀칭 과정은 HTML μ΅œμƒμœ„ λ…Έλ“œμ˜ λͺ¨λ“  λΆ€λΆ„ 집합을 탐색해 μœ μ‚¬ν•œ λ…Έλ“œλ“€μ„ μ°ΎμŠ΅λ‹ˆλ‹€. μ΄λ•Œ μ‚¬μš©μž μ •μ˜μ— 따라 정확도λ₯Ό μ„€μ •ν•˜μ—¬ λΉ„μŠ·ν•œ νŒ¨ν„΄λ“€λ„ 찾을 수 μžˆμŠ΅λ‹ˆλ‹€.

μ™Όμͺ½μ€ μ–΄λ–€ νŒ¨ν„΄κ³Ό 100% μœ μ‚¬λ„λ₯Ό κ°–λŠ” 경우λ₯Ό νƒμƒ‰ν•œ 결과이고, 였λ₯Έμͺ½μ€ 80% μ΄μƒμ˜ μœ μ‚¬λ„λ₯Ό κ°–λŠ” 경우λ₯Ό νƒμƒ‰ν•œ κ²°κ³Όμž…λ‹ˆλ‹€.

λ§Œμ•½ νŒ¨ν„΄λ“€μ˜ LCAκ°€ νŒ¨ν„΄λ“€μ„ ν•˜μœ„λ…Έλ“œλ‘œ κ°–λŠ”λ‹€λ©΄ λ°˜λ³΅λ¬Έμ„ ν†΅ν•œ 접근이 κ°€λŠ₯ν•΄μ§‘λ‹ˆλ‹€. 2.2 절 ν•˜λ‹¨μ˜ μ½”λ“œλ₯Ό μ°Έκ³ ν•΄λ³΄μ„Έμš”. 이와 같은 μ½”λ“œλ₯Ό μ‰½κ²Œ 생성해 λ‚Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

2.4.3. 크둀링 μ½”λ“œ 생성

100%의 μ •ν™•λ„λ‘œ νŒ¨ν„΄μ„ 찾은 경우라면 λͺ¨λ“  과정을 λͺ¨λ“  λ…Έλ“œμ— μ™„λ²½ν•˜κ²Œ μ μš©μ‹œν‚¬ 수 μžˆμ§€λ§Œ, μœ μ‚¬λ„κ°€ λ‹€λ₯Έ 것듀이 μ„žμ—¬μžˆλ‹€λ©΄ 이λ₯Ό λΆ„λ₯˜ν•˜λŠ” 과정이 ν•„μš”ν•©λ‹ˆλ‹€.

μœ„μ™€ 같이 λŒ“κΈ€μ΄ ν•˜λ‚˜ 이상 달린 κΈ€μ—λŠ” λŒ“κΈ€μ˜ κ°œμˆ˜κ°€ 제λͺ©μ— ν‘œμ‹œλ˜μ–΄μžˆμ§€λ§Œ, λŒ“κΈ€μ΄ μ—†λŠ” κ²½μš°μ—” ν‘œμ‹œλ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ λͺ¨λ“  λ…Έλ“œμ— λŒ€ν•΄ λŒ“κΈ€μ˜ 개수λ₯Ό κ°€μ Έμ˜€λŠ” 뢀뢄을 λ„£λŠ”λ‹€λ©΄ 였λ₯˜κ°€ 생길 수 μžˆμŠ΅λ‹ˆλ‹€. μ½”λ“œλ₯Ό μƒμ„±ν•˜κΈ°μ „ μ „μˆ˜μ‘°μ‚¬λ₯Ό 톡해 μ΄λŸ¬ν•œ μ–΄κΈ‹λ‚œ νŒ¨ν„΄λ“€μ„ λΆ„μ„ν•˜λŠ” 과정이 ν•„μš”ν•˜λ©°, μ½”λ“œ μƒμ„±μ‹œ 이λ₯Ό μ°Έκ³ ν•΄ μ μ ˆν•œ μ½”λ“œλ₯Ό 생성할 수 μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€.

λ‹€μŒμ€ νŒ¨ν„΄ 뢄석 μ •λ³΄μ˜ μΌλΆ€μž…λ‹ˆλ‹€.

-- Captures Info remove LCA Prefix --
@Number = /td[1]
@Icon = /td[2]/a[1]/em[1]
@Title = /td[2]/a[1]
@Comment = /td[2]/a[2]/span[1]
@Author = /td[3]
@WriteTime = /td[4]
@Views = /td[5]
@UpVote = /td[6]

-- Captures Info Origin --
@Number = /html[1]/body[1]/div[2]/div[2]/main[1]/section[1]/article[2]/div[2]/table[1]/tbody[1]/tr[4]/td[1]
@Icon = /html[1]/body[1]/div[2]/div[2]/main[1]/section[1]/article[2]/div[2]/table[1]/tbody[1]/tr[4]/td[2]/a[1]/em[1]
@Title = /html[1]/body[1]/div[2]/div[2]/main[1]/section[1]/article[2]/div[2]/table[1]/tbody[1]/tr[4]/td[2]/a[1]
@Comment = /html[1]/body[1]/div[2]/div[2]/main[1]/section[1]/article[2]/div[2]/table[1]/tbody[1]/tr[4]/td[2]/a[2]/span[1]
@Author = /html[1]/body[1]/div[2]/div[2]/main[1]/section[1]/article[2]/div[2]/table[1]/tbody[1]/tr[4]/td[3]
@WriteTime = /html[1]/body[1]/div[2]/div[2]/main[1]/section[1]/article[2]/div[2]/table[1]/tbody[1]/tr[4]/td[4]
@Views = /html[1]/body[1]/div[2]/div[2]/main[1]/section[1]/article[2]/div[2]/table[1]/tbody[1]/tr[4]/td[5]
@UpVote = /html[1]/body[1]/div[2]/div[2]/main[1]/section[1]/article[2]/div[2]/table[1]/tbody[1]/tr[4]/td[6]

-- Available Captures Info --
@Number = /html[1]/body[1]/div[2]/div[2]/main[1]/section[1]/article[2]/div[2]/table[1]/tbody[1]/tr[4]/td[1]
@Icon = /html[1]/body[1]/div[2]/div[2]/main[1]/section[1]/article[2]/div[2]/table[1]/tbody[1]/tr[4]/td[2]/a[1]/em[1]
@Title = /html[1]/body[1]/div[2]/div[2]/main[1]/section[1]/article[2]/div[2]/table[1]/tbody[1]/tr[4]/td[2]/a[1]
@Author = /html[1]/body[1]/div[2]/div[2]/main[1]/section[1]/article[2]/div[2]/table[1]/tbody[1]/tr[4]/td[3]
@WriteTime = /html[1]/body[1]/div[2]/div[2]/main[1]/section[1]/article[2]/div[2]/table[1]/tbody[1]/tr[4]/td[4]
@Views = /html[1]/body[1]/div[2]/div[2]/main[1]/section[1]/article[2]/div[2]/table[1]/tbody[1]/tr[4]/td[5]
@UpVote = /html[1]/body[1]/div[2]/div[2]/main[1]/section[1]/article[2]/div[2]/table[1]/tbody[1]/tr[4]/td[6]

-- Pattern Info --
P-Summary: (tr#@Number#(td#@Title#(a@Comment)#)#@Author#@WriteTime#@Views#@UpVote#)
P-LCA: /html[1]/body[1]/div[2]/div[2]/main[1]/section[1]/article[2]/div[2]/table[1]/tbody[1]/tr[4]
LCA: /html[1]/body[1]/div[2]/div[2]/main[1]/section[1]/article[2]/div[2]/table[1]/tbody[1]

-- Capture result from Page --
Count: 40
...
-------------------------
test-case: #21
tc-lca: /html[1]/body[1]/div[2]/div[2]/main[1]/section[1]/article[2]/div[2]/table[1]/tbody[1]/tr[24]
@Number = 1257146
@Icon = /em:{class="icon_img icon_txt"}
@Title = 근데 μ†”μ§νžˆ κ°œλ°œμžλŠ” μ™œν•˜λŠ”κ±°μž„? 이해가 μ•ˆλ¨
@Comment = [6]
@Author = γ…‡γ…‡(106.101)
@WriteTime = 02.12
@Views = 48
@UpVote = 0
...

Available Captures Infoλ₯Ό 보면 @Commentκ°€ λΉ μ ΈμžˆλŠ” 것을 λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” μ½”λ“œμƒμ„±μ‹œ @Commentλ₯Ό 핸듀링할 수 μžˆλŠ” μ μ ˆν•œ μ½”λ“œλ₯Ό μ‚½μž…ν•˜λΌλŠ” 의미둜 λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€. 이제 μœ„ 정보λ₯Ό μ΄μš©ν•΄ λ‹€μŒ μ½”λ“œλ₯Ό 얻을 수 μžˆμŠ΅λ‹ˆλ‹€.

public class Pattern
{
    public string Number;
    public string Icon;
    public string Title;
    public string Comment;
    public string Author;
    public string WriteTime;
    public string Views;
    public string UpVote;
}

public List<Pattern> Extract(string html)
{
    HtmlDocument document = new HtmlDocument();
    document.LoadHtml(html);
    var root_node = document.DocumentNode;
    var result = List<Pattern>();
    for (int i = 1; ; i++)
    {
        var node = root_node.SelectSingleNode($"/html[1]/body[1]/div[2]/div[2]/main[1]/section[1]/article[2]/div[2]/table[1]/tbody[1]/tr[{3+i*1}]");
        var pattern = new Pattern();
        if (node == null) break;
        pattern.Number = node.SelectSingleNode("./td[1]").InnerText;
        pattern.Icon = node.SelectSingleNode("./td[2]/a[1]/em[1]").InnerText;
        pattern.Title = node.SelectSingleNode("./td[2]/a[1]").InnerText;
        if (node.SelectSingleNode("./td[2]/a[2]/span[1]") != null)
            pattern.Comment = node.SelectSingleNode("./td[2]/a[2]/span[1]").InnerText;
        pattern.Author = node.SelectSingleNode("./td[3]").InnerText;
        pattern.WriteTime = node.SelectSingleNode("./td[4]").InnerText;
        pattern.Views = node.SelectSingleNode("./td[5]").InnerText;
        pattern.UpVote = node.SelectSingleNode("./td[6]").InnerText;
        result.Add(pattern);
    }
    return result;
}

2.5 차이점 뢄석

차이점 뢄석은 두 HTML의 차이점을 λΆ„μ„ν•˜λŠ” λ°©λ²•μž…λ‹ˆλ‹€. 이 κΈ°λŠ₯이 ν•„μš”ν•œ 경우의 μ˜ˆλ‘œλŠ” 두 κ²Œμ‹œλ¬Όμ˜ 차이점을 뢄석해 제λͺ©, 글쓴이, λ³Έλ¬Έ λ“±μ˜ 정보λ₯Ό μžλ™μœΌλ‘œ μ°Ύμ•„λ‚΄λŠ” 경우 등이 μžˆμŠ΅λ‹ˆλ‹€. 차이점 뢄석은 μ—¬λŸ¬ λ‹€λ₯Έ 정적 뢄석 λ°©λ²•λ“€μ΄λž‘ 같이 μ‚¬μš©ν•  λ•Œ 생산성을 λ”μš± λ†’νž 수 μžˆμ„ κ²ƒμœΌλ‘œ κΈ°λŒ€ν•©λ‹ˆλ‹€.

차이점 λΆ„μ„μ˜ 기본적인 μ•„μ΄λ””μ–΄λŠ” 맀우 λ‹¨μˆœν•˜κ²Œ λͺ¨λ“  λ…Έλ“œλ₯Ό μˆœμ„œλŒ€λ‘œ λ˜‘κ°™μ΄ λ°©λ¬Έν•˜μ—¬ λ‹€λ₯Έ 뢀뢄을 λͺ¨λ‘ μ°Ύμ•„λŠ” κ²ƒμž…λ‹ˆλ‹€. λ§Œμ•½ νƒœκ·Έκ°€ λ‹€λ₯΄κ±°λ‚˜, μžμ‹ λ…Έλ“œμ˜ κ°œμˆ˜κ°€ λ‹€λ₯΄λ‹€λ©΄ 더이상 λ°©λ¬Έν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 이와 같이 두 루트 λ…Έλ“œλ₯Ό λ™μ‹œμ— λ°©λ¬Έν•˜κ²Œ 되면 λ‹€μŒκ³Ό 같은 λ„€ 가지 경우의 차이점이 λ°œμƒν•©λ‹ˆλ‹€.

1. νƒœκ·Έκ°€ λ‹€λ₯Έ 경우
2. 속성이 λ‹€λ₯Έ 경우
3. μžμ‹ λ…Έλ“œμ˜ κ°œμˆ˜κ°€ λ‹€λ₯Έ 경우
4. ν¬ν•¨λœ ν…μŠ€νŠΈκ°€ λ‹€λ₯Έ 경우

νƒœκ·Έκ°€ λ‹€λ₯Έ 경우라면 ν•˜μœ„ λ…Έλ“œλ“€λ„ λͺ¨λ‘ λ°”λ€Œμ—ˆμ„ κ°€λŠ₯성이 있으며, μ΄λŠ” μ›Ή νŽ˜μ΄μ§€λ₯Ό 뢈러 올 λ•Œλ§ˆλ‹€, ν˜Ήμ€ λ‹€λ₯Έ URL Parameter둜 μ›Ή νŽ˜μ΄μ§€λ₯Ό λ‘œλ”©ν•  λ•Œλ§ˆλ‹€ λ‹€λ₯Έ λ…Έλ“œκ΅¬μ‘°λ₯Ό κ°–λŠ” λ‹€λŠ” 의미둜 λ³Ό 수 μžˆμœΌλ―€λ‘œ, λ”°λΌμ„œ 이 뢀뢄이 제λͺ©μ΄λ‚˜ 본문같은 핡심이 될 뢀뢄이 될 κ°€λŠ₯성이 λ†’λ‹€λŠ” 것을 μ˜λ―Έν•©λ‹ˆλ‹€. λ‹€λ§Œ, νƒœκ·Έκ°€ λ‹€λ₯Έ κ²½μš°κ°€ λ°œμƒν•˜λŠ” κ²½μš°λŠ” 맀우 맀우 λ“œλ¬Όλ‹€κ³  ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

속성이 λ‹€λ₯Έ 경우라면 λ‹€λ₯Έ μ›Ή λ¦¬μ†ŒμŠ€λ₯Ό λΆˆλŸ¬μ˜€λŠ” κ²½μš°κ°€ λŒ€λΆ€λΆ„μ΄λ―€λ‘œ(예λ₯Ό λ“€μ–΄ <img> νƒœκ·Έμ˜ src 속성) μ΄λŠ” μ›Ή νŽ˜μ΄μ§€λ₯Ό 뢈러올 λ•Œλ§ˆλ‹€ λ‹¬λΌμ§ˆ κ°€λŠ₯성이 λ†’λ‹€λŠ” 것을 μ˜λ―Έν•©λ‹ˆλ‹€.

μžμ‹ λ…Έλ“œμ˜ κ°œμˆ˜κ°€ λ‹€λ₯Έ κ²½μš°λŠ” λΉ„μŠ·ν•œ μœ ν˜•μ˜ 두 μ›ΉνŽ˜μ΄μ§€λ₯Ό λΆ„μ„ν•˜λŠ” κ²½μš°μ—” 거의 λ‚˜νƒ€λ‚˜μ§€ μ•ŠλŠ” μ°¨μ΄μ μž…λ‹ˆλ‹€. μ—¬κΈ°μ„œ κ±°μ˜λΌλŠ” 것은 λ§Žμ•„μ•Ό 두 개 정도이고, 보톡 0κ°œλ‚˜ 1κ°œμž„μ„ μ˜λ―Έν•©λ‹ˆλ‹€. 이 뢀뢄이 본문일 κ°€λŠ₯성이 κ°€μž₯ 높은 λΆ€λΆ„μž…λ‹ˆλ‹€. λ”°λΌμ„œ 본문을 μ°Ύκ³  μ‹Άλ‹€λ©΄ μžμ‹ λ…Έλ“œμ˜ κ°œμˆ˜κ°€ λ‹€λ₯Έ 경우λ₯Ό λ¨Όμ € μ‚΄νŽ΄λ³΄λŠ”κ²Œ κ°€μž₯ 쒋은 방법이라고 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

ν¬ν•¨λœ ν…μŠ€νŠΈκ°€ λ‹€λ₯Έ κ²½μš°λŠ” μ›Ή νŽ˜μ΄μ§€λ§ˆλ‹€ λ™μΌν•œ ꡬ쑰(μŠ€νƒ€μΌ)둜 UIκ°€ μ œκ³΅λ˜μ§€λ§Œ κ·Έ μ•ˆμ˜ 컨텐츠가 λ‹€λ₯΄λ‹€λŠ” μ˜λ―Έμž…λ‹ˆλ‹€. λ”°λΌμ„œ 제λͺ©μ΄λ‚˜ 글쓴이와 같은 컨텐츠듀을 νƒμƒ‰ν•˜κ³ μž ν•  λ•Œ 이 뢀뢄을 λ¨Όμ € μ‚΄νŽ΄λ³΄λŠ”κ²Œ μ’‹μŠ΅λ‹ˆλ‹€.

μœ„ 사진은 ν¬ν•¨λœ ν…μŠ€νŠΈκ°€ λ‹€λ₯Έ 경우의 μ˜ˆμ œμž…λ‹ˆλ‹€.

μ•„λž˜ 사진은 μžμ‹ λ…Έλ“œμ˜ κ°œμˆ˜κ°€ λ‹€λ₯Έ 겨우의 μ˜ˆμ œμž…λ‹ˆλ‹€.

3. 동적 뢄석

동적 뢄석에 κ΄€ν•œ λ‚΄μš©μ€ λ‹€μŒμ„ μ°Έκ³ ν•΄μ£Όμ„Έμš”

https://blog.naver.com/rollrat/221905945071

https://blog.naver.com/rollrat/221906735915

https://blog.naver.com/rollrat/221908229380

https://github.com/rollrat/custom-crawler/blob/master/Documents/Dynamics.md