자바스크립트를 활성화 해주세요

p022 AngleSharp을 Wrap한 AngleParse와 Pwsh로 즐거운 Scrapping 하기

 ·  ☕ 4 min read

최근 닷넷에서 Crawling은 Html Agility Pack 보다 AngleSharp을 사용하고 있습니다. XPath보다 CSS Path가 프론트엔드 개발에도 사용하고 있는 탓에 더 익숙해져있기 때문입니다. 이 포스트에서는 AngleSharp를 Wrap한 AngleParse를 이용해 PowerShell에서 사용하는 방법을 살펴보았습니다.

AngleParse

AngleParse는 AngleSharp을 Wrap한 PowerShell Module입니다.

1
2
install-module angleparse
import-module angleparse

Powershell Core에서도 잘 동작합니다만, Core가 아닌 윈도우즈에서 실행하려면 netstandard가 필요하다는 메시지가 나옵니다.

PS C:\Users\Administrator> $host | select name,version

Name        Version
----        -------
ConsoleHost 5.1.14393.3471

PS C:\Users\Administrator> import-module angleparse
import-module : ファイルまたはアセンブリ 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13f
1'、またはその依存関係の 1 つが読み込めませんでした。指定されたファイルが見つかりません。
発生場所 行:1 文字:1
+ import-module angleparse
+ ~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Import-Module], FileNotFoundException
    + FullyQualifiedErrorId : System.IO.FileNotFoundException,Microsoft.PowerShell.Commands.ImportModuleCommand

타켓 URI와 CSS

타겟 URI는 news.daum.net 로 잡고

타켓 CSS

타켓 CSS는 적당히 html tag와 class, id 를 읽을줄 아신다면, Chrome의 개발자 콘솔을 띄우고 적당히 찾으시면 됩니다. 예를 들면 다음의 이미지에서처럼, 정확한 CSS Path는 “body #Outer #Inner #Content .boxed #lipsum"입니다만, CSS Path는 “#Outer #Inner"를 생략하고 “body #Content .boxed #lipsum"로 접근하여도 접근이 됩니다.

p022_angleparse.png

보는 방법은 다음의 페이지를 참고하셔도 됩니다.

https://webliker.info/css-selector-cheat-sheet/

1
iwr "https://www.lipsum.com/feed/html" | Select-HtmlContent "body #Content .boxed #lipsum"

또다른 적당한 방법은 Chrome Extension를 이용하는 것입니다. CSS Selector라는 키워드로 검색하면 도움이 되실겁니다.

예를 들어 Chrome Extension의 CSS Selector Helper for Chrome 를 이용하신다면 다음의 방법을 이용할 수 있습니다.

  1. Control+Shift+I 를 눌러서 개발자 콘솔을 띄웁니다.
  2. 먼저 엘리먼트를 하나 선택하고, 오른쪽 판넬의 메뉴부분을 보면 CSS Selector가 추가되어 있습니다.
  3. Selector to Clipboard를 선택해서 클립보드로 복사해 넣습니다.

저는 설치는 해봤지만 실제로는 코드를 보는 방법을 좀 더 자주 사용하는 편입니다.

기본기 1

기본적인 사용법은 다음과 같습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
PS C:\Users\Administrator> $host.Version

Major  Minor  Build  Revision
-----  -----  -----  --------
7      0      3      -1

PS C:\Users\Administrator> import-module angleparse

PS C:\Users\Administrator> '<div><span class="foo">text content here</span></div>' | Select-HtmlContent "div > span.foo"
# "text content here"

PS C:\Users\Administrator> '<div><span>abc</span></div>' | Select-HtmlContent "div > span", ([regex]'a(bc)')
# "bc"

PS C:\Users\Administrator> '<div><span>abc</span></div>' |
Select-HtmlContent "div > span" |
% { $_ | Select-HtmlContent ([regex]'a(bc)') }
# "bc"

PS C:\Users\Administrator> '<div class="a">1a</div><div class="b">2b</div>' |
select-htmlcontent "> div",
@{ Class = ([AngleParse.Attr]::Class);
   NumPlus1 = ([regex]'(\d)\w'), { [int]$_ + 1 } }

기본기 2

위에서 예를 들었던 https://www.lipsum.com 의 본문을 crawling해 보면

1
PS C:\Users\Administrator> iwr "https://www.lipsum.com/feed/html" | Select-HtmlContent "body #Outer #Inner #Content .boxed #lipsum"

네, 테스트용으로 자주쓰이는 의미없는 문자열인 Lorem ipsum 문자열이 출력되면 맞게 실행된 것입니다.

기본기 3

다음은, Chrome Extension의 CSS Selector Helper for Chrome을 이용하여 CSS Path를 선택한 내용입니다.

1
iwr "https://news.daum.net/" | Select-HtmlContent "body.bg_news div#kakaoWrap div#kakaoContent.cont_home[role='main'] #cSub [data-tiara-layer='article_main']"

결과

선택된 element가 하나가 아니어서, 다음과 같이 복수개의 element가 표시됩니다.

1
2
3
4
5
PS C:\Users\Administrator> iwr "https://news.daum.net/" | Select-HtmlContent "body.bg_news div#kakaoWrap div#kakaoContent.cont_home[role='main'] #cSub [data-tiara-layer='article_main']"
'시험지 유출' 숙명여고 쌍둥이 유죄.. "공교육 신뢰 무너뜨려"
트럼프  찔렸다..바이든 능가하는 카멀라 해리스의 강점 '셋'
'4차 추경' 이틀만에 물러선 민주당.. 홍남기 '1승' 지키려나
7월 가계대출 7조6000억.. 동월 기준 최대

기본기 4

해당 element들을 다시 parsing하여 PSCustomObject로 리턴하는 방법도 있습니다. 다음의 예를 보시면 어느정도 감을 잡으실 수 있으실 겁니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
iwr "https://b.hatena.ne.jp/" | Select-HtmlContent "div.entrylist-contents", @{ 
    User = "span.entrylist-contents-users > a > span", { [int]$_ }
    Title = "h3.entrylist-contents-title > a"
    Uri = "h3.entrylist-contents-title > a", ([AngleParse.Attr]::Href)
    Time = "li.entrylist-contents-date", ([regex]'\S+ (\d{2}:\d{2})')
    Tags = "a[rel=tag]"
} | select -first 2


# Title : スク水揚げ、シマに活気 南城市奥武島 - 琉球新報 - 沖縄の新...
# User  : 507
# Uri   : https://ryukyushimpo.jp/news/entry-1160519.html
# Tags  : {沖縄, ネタ, 漁業, スク水…}
# Time  : 06:13

# Title : 女の裸を生で見たかったんだ
# User  : 419
# Uri   : https://anond.hatelabo.jp/20200704002540
# Tags  : {増田, 性, あとで読む, 人生…}
# Time  : 06:24

속성 Selector

1
2
'<a href="https://foo.go.jp">bar</a>' | Select-HtmlContent ([AngleParse.Attr]::Href)
# "https://foo.go.jp"

AngleParse.Attr 라는 열거형값을 사용하는 것이 가능합니다. Attr은 다음의 11가지를 사용할 수 있습니다.

  • Element
  • InnerHtml
  • OuterHtml
  • TextContent
  • Id
  • Class
  • SplitClasses
  • Href
  • Src
  • Title
  • Name

정규표현 Selector

1
2
'<span>2020/07/22</span>' | Select-HtmlContent ([regex]'(\d{4})/(\d{2})/\d{2}')
# "2020", "07"

ScriptBock Selector

PowerShell이 낯설으신 분들은 ScriptBock이 뭐지? 라고 생각하실 수도 있는데 무명함수 같은 것으로 생각하시면 됩니다.

입력은 DOM, 문자열 뿐만 아니라 어떤 오브젝트라도 받습니다. 또한 출력은 어떤 형태의 값도 오브젝트에 박스화되어 출력됩니다.

1
2
'<span>7</span>' | Select-HtmlContent { [int]$_ * 6; [int]$_ * 7 }
# 42, 49

결론

다음과 같으신 분들에게는 AngleParse를 추천합니다.

  • XPath보다는 CSS Path가 익숙하신 분.
  • Pwsh 7 사용에 불편함이 없으신 분.
  • CSharp 쓸 일은 별로 없고, Powershell할 일은 많으신 분

Powershell 5.1을 사용해야만 하는 상황이면, iexplorer의 COM버전인 ParsedHtml의 사용이 손쉬울 수도 있습니다.

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 <# using TLS 1.2 is vitally important #>
$req = Invoke-Webrequest -URI "www.lipsum.com/feed/html"
$loremIpsum = $req.ParsedHtml.getElementById('lipsum').textContent

레퍼런스

공유하기

tkim
글쓴이
tkim
Software Engineer