웹 스크래핑과 크롤링으로 가장 많이 쓰이는 건 아무래도 Python일 겁니다. 라이브러리도 잘 되어 있고 빠르니까요. 하지만 코드를 한 번도 짜 본적 없는 사람이 웹 스크래핑과 크롤링을 하고 싶어서 파이썬을 배우기 시작하면 꽤 오랜 시간이 필요할 겁니다. 영어 하나 배우는 데에도 오래 걸리는데, 파이썬이라는 컴퓨터 언어는 익숙하지 않으니 더 배우기 어려울 수밖에 없죠.

최근에 구글 스프레드시트로 웹 스크래핑을 하는 법을 알게 됐습니다. 주로 파이썬으로 웹 스크래핑을 하곤 했었는데, 구글 스프레드시트에서도 스크래핑을 편하게 할 수 있도록 누가 자바스크립트 코드를 만들어 뒀더라구요. 저도 인터넷으로 찾아서 해보다 알게 됐는데, 사용 방법을 공유해 두면 좋을 것 같아서 포스팅해 보려고 합니다.

사실 웹 스크래핑을 제대로 하려면 웹 브라우저의 의미, html document와 Network에서 웹 브라우저로 데이터를 쏘는 방식 등등 알아야 할 게 많지만, 그런 거 몰라도 일단 따라하다 보면 스크래핑 되는 사례를 중심으로 써 보겠습니다.



최소한의 지식만 설명드리자면, 웹 브라우저는 우리가 접속한 사이트를 운영하는 회사 내 서버에서 데이터를 전송받아 우리가 볼 수 있게 표시해 줍니다. 여기서 회사 서버가 웹 브라우저로 데이터를 보내는 방식은 크게 세 가지라고 보면 됩니다. html 형식, xml 형식, 그리고 json 형식. 지금부터 설명하는 데이터 스크래핑 방법은 json 형식으로 웹 브라우저가 데이터를 받는 형태에서만 동작합니다. html이나 xml 방식으로 데이터를 받은 웹 브라우저를 스크래핑하려면, 구글에 'google spreadsheet importxml'라고 검색하면 됩니다.


웹 스크래핑하려는 사이트로 저는 Klook.com을 쓰려고 합니다.
https://www.klook.com/ko/
해외여행을 가서 할 수 있는 활동이나 입장권 등을 판매하는 사이트입니다.



이를테면 '오사카'로 해외여행을 가려 하는데, Klook 사이트에서 오사카 관련한 액티비티 상품 top10을 구글 스프레드시트로 추출하고 싶다고 해 보겠습니다. 먼저 크롬 브라우저로 Klook 오사카 액티비티 사이트로 들어갑니다.



F12를 누릅니다. 개발자도구 창이 뜹니다.
Element가 선택되어 있다면 Network로 바꿔 줍니다.

그리고 새로고침을 한 번 누르면, 아래 사진처럼 뭐가 많이 뜹니다.



웹 스크래핑에 필요한 데이터는 XHR에 거의 다 있다고 보면 됩니다. 
아래 사진처럼 XHR을 선택한 다음,
사이트의 자체 필터로 'Klook 추천'이라 선택되어 있는 걸 '평점순'으로 바꿔 봅니다.



그럼 위의 사진에서 보이는 링크가 맨 아래에 하나 뜹니다. 클릭하면 header라는 선택창 아래 뭐가 많습니다.
일단은 무시해 주고, 바로 옆에 있는 Preview를 눌러 줍니다.



그럼 error랑 result가 있을 텐데, result 안을 들여다보면 액티비티에서 제공하는 상품의 정보가 순서대로 담겨 있는 걸 볼 수 있습니다.

이렇게 Preview창까지 들어왔을 때 데이터가 이런 형태로 나열돼 있는 걸 json 형태 데이터라고 보면 됩니다.




여기까지 왔으면 데이터는 거의 다 가져온 겁니다. 이제 Name 아래에 있는 activities?page=1&order=1...이라 써 있는 저 링크를 기억하세요. 마우스 오른쪽 버튼 누르고 copy link address를 선택하면 url주소를 복사할 수 있습니다.




이제 구글 스프레드시트로 가 보겠습니다.  도구 - 스크립트 편집기를 누릅니다.



그러면 이런 창이 나타납니다.




이제 여기에 importjson이라는 자바스크립트 함수를 넣을 차례입니다. 안에 써 있는 내용을 다 지우고, 아래 사이트에 들어가서 보이는 모든 코드를 복사해 붙여넣습니다.
https://gist.github.com/chrislkeller/5719258
그리고 코드를 입력한 파일 이름을 importjson.gs로 변경해 줍니다.



다시 구글 스프레드시트로 돌아가서, A1 셀에 아까 기억하라고 했던 url을 넣어 줍니다. activities?page=1... 그 url을 넣습니다.
그리고 그 옆 셀에 아래의 명령어를 복사해 넣어줍니다.

=query(importjson($A$1,"/result/item/"),"select * limit 11")
#이 함수식에서 알아야 할 내용은 두 가지입니다. #1. "/result/item/"의 의미는, 아까 계층적 구조에서 result 아래 item이 있고, #그 아래에 0,1,2,3 순으로 데이터가 나열돼 있을 때 item 아래에 있는 모든 값을 불러오라는 의미입니다. #만약 item 안에 title과 marketprice만 가져오고 싶다면 #"/result/item/title,/result/item/market_price"라고 입력하면 됩니다. #2. select * limit 11은 위에서부터 10개의 값을 불러오라는 의미입니다. #만약 20개를 불러오고 싶다면 21을 적으면 됩니다. #참고로, 이 페이지에서 /result/item/ 안에 있는 데이터의 개수는 24개입니다. #즉 limit 25 이상을 적어도 24개 이상으로는 불러올 수 없습니다. 
#만약 24개 이후의 걸 가져오고 싶다면, url에서 page=1을 page=2로 바꾸면 됩니다.


명령어를 넣으면 아래처럼 결과가 나옵니다.



좀 이상합니다. 분명 아까 개발자도구로 봤을 때는 한글이었는데, 스크래핑한 결과는 다 영어입니다. 하지만 id번호나 title 내용을 읽어 보면 대충 비슷한 데이터들입니다. 완전히 잘못 가져온 건 아닌 것 같은데, title과 subtitle이 전부 영어고 market price와 sell price도 한국 원화 기준이 아닙니다.

이 문제를 해결해야 하니, 스크립트 편집기와 개발자도구 열어 둔 Klook 페이지를 다시 보겠습니다.





header로 돌아가서 스크롤을 내리다 보면 Request header가 있습니다. 

여기 값들을 보다 보면 뭔가 한글로 변환하라는 지시처럼 보이는 설정이 있을 겁니다. 

Accept-Language:  ko_KR이라는 거나, Currency: KRW 같은 값이 보이죠. 

뭔가 이 설정 때문에 한글로 변환되어 웹페이지에 출력되는 것 같습니다.
그렇다면 이 설정을 importjson 코드에 넣어 줘야 합니다. 이제 스크립트 편집기로 돌아갑니다.




여기 있는 많은 코드들 중에서 ImportJSONAdvanced 함수를 변환해 줘야 합니다. 

ctrl+f로 importJsonAdvanced를 검색해서, 그 함수에 정의된 값 전체를 아래 값으로 덮어씌웁니다.

function ImportJSONAdvanced(url, query, options, includeFunc, transformFunc) {

#아까 본 Request header에 있는 값을 aa라는 변수로 정의합니다. #'Host'값이 살아 있으면 함수가 작동하지 않습니다. 주석 처리해 줍니다. var aa={ // "Host": "www.klook.com", "Connection": "keep-alive", "Currency": "KRW", "Accept-Language": "ko_KR", "x-platform": "desktop", "Accept": "application/json, text/javascript, */*; q=0.01", "X-Requested-With": "XMLHttpRequest", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36", "version": "4", "Referer": "https://www.klook.com/ko/city/29-osaka/?p=1", "Accept-Encoding": "gzdip, deflate, br", "Cookie": "optimizelyEndUserId=oeu1514449940676r0.7215016123782034; optimizelySegments=%7B%226247520328%22%3A%22false%22%2C%226245800241%22%3A%22gc%22%2C%226251180312%22%3A%22search%22%7D; optimizelyBuckets=%7B%7D; _ga=GA1.2.422684579.1514449941; _gid=GA1.2.1394135673.1514449941; _vwo_uuid_v2=8727253FE50002B0CDD42E20D7885C3C|700dc7807cded5b7a09aaa3e655d39c2; setidd=1514449941556-871200748; tag_fok=1514449941000; scs=%7B%22t%22%3A1%7D; spUID=1514449941748903bb7751a.558a36d6; klk_lang=ko; klk_currency=KRW; optimizelyPendingLogEvents=%5B%5D; _dc_gtm_UA-86696233-1=1; JSESSIONID=4A14A8E768EF393F4005294124807D28; _gat=1; inLanding=https%3A%2F%2Fwww.klook.com%2Fko%2F; ins-gaSSId=dbb70e55-c50c-27c3-ed87-766c7ed8c08a_1514512173; _gat_UA-54803406-1=1; _gat_UA-86696233-1=1; mp_c2ca8b423fd75a10792debf44cd6b51a_mixpanel=%7B%22distinct_id%22%3A%20%221609c4030d118e-0343788681a0bb-6524f51-1fa400-1609c4030d2f3a%22%2C%22%24search_engine%22%3A%20%22google%22%2C%22%24initial_referrer%22%3A%20%22https%3A%2F%2Fwww.google.co.kr%2F%22%2C%22%24initial_referring_domain%22%3A%20%22www.google.co.kr%22%2C%22__timers%22%3A%20%7B%7D%2C%22Language%22%3A%20%22ko%22%2C%22Platform%22%3A%20%22Web%22%7D; wcs_bt=s_2cb388a4aa34:1514508579; insdrSV=6; _uetsid=_uetcb19bb90; mp_mixpanel__c=5", "Cache-Control": "no-cache", "Postman-Token": "dd2d2017-d14b-2b2e-63f8-633fcfabf136" } var settings = { "async": true, "crossDomain": true, "url": "https://www.klook.com/xos_api/v1/websrv/cities/29/activities?page=1&order=0&limit=24&_=1514508578891", "method": "GET", "headers": aa }; #아까 정의한 aa라는 값을 "header"란에 넣어 줍니다. var object = JSON.parse(UrlFetchApp.fetch(url,settings).getContentText()); return parseJSONObject_(object, query, options, includeFunc, transformFunc); }


스크립트 에디터를 저장하고, 다시 구글 스프레드시트로 돌아갑니다.


이제 제대로 데이터를 가져오는 걸 확인할 수 있습니다.




구글 스프레드시트의 기능을 잘 응용하면, 이 함수를 통해 매 분, 매 시간 또는 하루에 한 번 꼴로 자동으로 데이터 스크래핑을 하도록 설정할 수 있습니다. 파이썬 웹 스크래핑보다 간편하고, 엑셀에 바로 반영되는 형식인데다, 실시간 또는 자동으로 웹 스크래핑을 할 수 있게 해 주는 편리한 기능입니다.


참고할 만한 사이트 : 
https://medium.com/@paulgambill/how-to-import-json-data-into-google-spreadsheets-in-less-than-5-minutes-a3fede1a014a

  1. 2018.04.13 15:53

    비밀댓글입니다

    • inspirit941 2018.04.13 15:55 신고

      보고자 하는 홈페이지의 url 주소를 예시로 올려주실 수 있나요? 웹페이지마다 request에 필요한 항목이 다를 수 있어서 각 페이지별로 확인해야 합니다.

    • 2018.04.13 16:02

      비밀댓글입니다

    • inspirit941 2018.04.13 16:06 신고

      inspirit941@gmail.com으로 보내주시면 됩니다. 하지만 이미지만으로는 해당 항목을 크롤링하는 법을 알 수가 없어서.. 우선 보내주시면 어느 웹서비스의 어떤 항목인지 찾아보겠습니다.
      그리고 제가 지금 회사라서.. 제대로 된 해답은 오늘 드리기엔 조금 어려울 수도 있습니다 :)

  2. 2018.04.13 16:09

    비밀댓글입니다

    • inspirit941 2018.04.14 00:18 신고

      네이버 포스트의 경우, 브라우저에 웹 데이터를 불러오는 방식이 json은 아닌 듯 보이네요. 따라서 importjson은 쓸 수 없습니다.
      오히려 댓글쓰는 창 바로 위에 있는 '시리즈' 란에 '000 읽음' 부분을 가져올 수 있을 듯합니다.
      http://m.post.naver.com/viewer/getRecommendPostView2.nhn?volumeNo=14851091&memberNo=11036773&vType=NO_SELECT&searchKeyword=&searchRank=&mainMenu=
      주소만 쳐도 브라우저가 불러올 수 있는 걸 보니 따로 header를 request할 필요는 없습니다.
      이 형태의 데이터를 불러오는 함수는 importxml입니다. 구글 검색으로 사용법을 확인하시면 될 듯 합니다.


      그리고 데이터가 나오는 행은 저도 정확히 컨트롤하는 방법을 모르겠습니다. 스크립트 내부 명령어를 손보면 어떻게든 할 수는 있겠지만... 저 긴 코드 뜯어보는 수고를 하느니 스프레드시트를 옮기는 게 속이 편해서요.

  3. 2018.04.29 13:36

    비밀댓글입니다

    • inspirit941 2018.05.08 20:55 신고

      https://steemit.com/api/@ineedthesleep/api-2

      이 게시물 아래 부분을 보시면 함수 자동실행 설정 방법이 있습니다. 참고하세요!

  4. 2018.05.13 15:30

    비밀댓글입니다

    • inspirit941 2018.05.15 20:53 신고

      importjson함수 안에 들어갈 셀 주소는 하나만 입력 가능합니다.
      따라서 각각의 셀에 독립적으로
      QUERY(importjson(($A$1),",.../items",name),"select * limit 1000")
      QUERY(importjson(($A$2),",.../items",name),"select * limit 1000")
      이렇게 나누어 쓰셔야 합니다.

  5. 코코넛냠냠 2018.09.13 16:49 신고

    와 대박 꿀팁; 좋은 글 올려 주셔서 감사합니다.

  6. 2018.11.13 01:27

    비밀댓글입니다

    • 2018.11.23 19:23

      비밀댓글입니다

  7. 데이터자동업데이트 2018.11.21 17:20

    유익한 글 감사합니다

    하나 질문드릴게 있는데

    혹시 데이터자동화업데이트(값이 수시로 바뀜)가 되는값을 받아서 구글 스프레드 시트를 작성하려할때도 위의 글에서 나와잇는 방법대로 하면 되나요??

    따로 해야되는건 없나요?

    답변 부탁드립니다 ㅠ

    • inspirit941 2018.11.23 19:20 신고

      위의 포스팅은 '특정 시점에서 네트워크가 브라우저에 제공하는 값'을 가져오는 방식입니다. 위의 방식으로는 값이 수시로 바뀌는 걸 바뀔 때마다 체크할 수는 없습니다. 함수를 매 초마다, 매분마다 실행해서 데이터를 수집하는 게 이 방식에서는 최선입니다.

  8. guest 2018.12.10 19:48

    트리거 말고 수동이나 자동으로 함수를 업데이트 해서 새로 json의 값을 받아오게 할 수는 없나요?

  9. bmo 2019.08.28 00:41

    읽어들이는걸 아래서부터 읽게할순없나요? 순서대로읽으니 과거의 게시글만 뽑히네요..

+ Recent posts