빅데이터 관련 프로그래밍/웹 크롤링 - scrapy & selenium

정적웹페이지를 [ 크롤링 ] –> [스크래핑] –> pipelines를 이용하여 데이터변형



동적웹페이지를 [ 스크래핑 ]까지만 하기


[ 파이썬 3.6 ]에서도 한방에 scrapy가 설치 가능하다.

  1. scrapy 설치 ( 각종 lxml 등 패키지와 함께 설치된다)
    [ conda install -c conda-forge scrapy ]
    https://doc.scrapy.org/en/latest/intro/install.html#installing-scrapy

  2. selenium 설치
    [ pip install -U selenium ]
    https://pypi.python.org/pypi/selenium




동적 웹페이지 웹 스크래핑 하기(scrapy+selenium)

동적 웹페이지인  premierleague.com를, scrapy + selenium을 활용하여,
2015/16 최종 순위표 url  : https://www.premierleague.com/tables?co=1&se=42&mw=-1&ha=-1 를 웹크롤링 해보자.


준비하기

  • 먼저, Prompt(1) scrapy shell용Prompt(2) jupyter notebook을 켜놓는다.

  • 접속하려는 url을 띄우고, 어떤 정보를 가져올지 생각해놓는다.
    순위/이름/경기수/승/무승부/패 / 골 득점/실점/골득실차/점수를 가져올 것이다.
    image


시작하기

  • 크롤링폴더인 web_da로 가서 scrapy startproject 프로젝트명 을 통해 크롤러의 새 프로젝트를 생성한다.
    scrapy startproject epl_crawler
    생성된 크롤러프로젝트는 jupyter로 확인한다

  • items.py를 열어서 item클래스를 정의해주자. item클래스에다가 각종 필드를 입력한다.
    image

  • spider폴더에 새로운 text-> epl_spider.py를 생성하여, Spider를 정의해주자.
    scrapy 및 epl_crawler폴더의 items.py의 EPLItem 클래스를  import해서 쓴다.
    (1) EPLSpider를 정의한다. 이 때,  크롤러이름 / 전체도메인 / 시작url을 정의한다.
    image
    (2) parse()함수를 정의한다. 이 때, parse()함수를 정의하기 위해서는 [크롬개발자도구 + shell]을 먼저 이용한다.

  • 해당 웹사이트를 shell에서 실행시켜놓고, 크롬개발자도구로 해당 웹사이트를 살펴봐야한다.
    image
    총 20개의 리스트가 있고, 각 item(행)에 속한 요소들을 가져올 것이다.
    20개의 리스트 상의 규칙성을 찾아, 한 item(행) 전체를 가져오게 할 것이다.
    (이전시간에는 상세페이지에서, 제목, 평점, 장르 각각의 xpath를 따로 구했는데,
    여기서는 리스트의 한 item를 먼저 가져올 것이다.)

    (1) 총 20개 리스트에서 한 item(행)의 요소 아무거나 [ctrl+shft+c]로 클릭한 뒤 타고 올라가보자.
        <tr> 요소들 중에서, class이름이 expandable인 것만 제외한 것을 구하면 된다.
       먼저, 첫번째 줄의 <tr>요소의 xpath를 가져와보자. 역시나 빈칸으로 셀렉터가 안가져와진다.
    image

    (2) 뒤에서부터 계층별로 살펴보면 된다.
    이 때, 맨 마지막 <tr>계층에서는 실제값을 가진 item이 “tableDark”와 “tableMid” 2가지 값을 가진 반면에 item이 없는 <tr>계층은 “expandable”만 있었다.
    tr의 인덱싱[] 란에,  tr[ not(@class="expandable") ]을 통해, 실제 필요한 class를 가져올 수 있게 되었다.
    그리고 각 계층을 따라올라가면서 필요없는 계층은 삭제해주고, 틀린인덱싱도 순서대로 세서 바르게 인덱싱시켜줘야한다.
    response.xpath('//*[@id="mainContent"]/div[2]/div[3]/div/div/div/table/tbody/tr[not(@class="expandable")]  ')
    image
    (3) 파이썬리스트 형태로 얻어진 item의 셀렉터리스트들을 rows라는 변수에 저장한 뒤,
       rows[0] 인덱싱으로 하나의 row만 따본다.
    rows = response.xpath('//*[@id="mainContent"]/div[2]/div[3]/div/div/div/table/tbody/tr[not(@class="expandable")]')
    row = rows[0]
    이 때, row에는 하나의 <tr>요소의 xpath셀렉터가 담겨있고, 
    .xpath( ‘./ ~~~ ’)로 추가할 <tr>요소 밑의 몇번째 <td>요소를 가져오냐에 따라서, 각 데이터값이 정해진다.
    <전체 item의 셀렉터리스트이므로, parse함수의 for문의 인자로 들어갈 것이다.>
    image
  • 첫번 째 [순위]의 경우, tr요소 밑-> 2번째 td 밑 –> span요소의 text로서 숨겨져 있었다.
    row(첫번째 item의 <tr>까지의 셀렉터)에 해당자식들까지 xpath를 추가한 다음, [0].extract()로 셀렉터->만 뽑아내보자.
    row.xpath('./td[2]/span/text()')[0].extract()
    image

  • 이제 [경기수]를 찍어내보자.  전체리스트[rows]의 첫번째[0] item의 <tr>요소 셀렉터인 row변수에,
    4번째 td요소까지 간 다음, text를 뽑으면 될 것이다.
    row.xpath('./td[4]/text()')[0].extract()
    1위팀의 경기 수 이므로, 38경기가 나와야하는데, shell에서는 29경기가 찍힌다.
    이것이 바로, 기본 정적페이지를 띄워놓고 –> 동적으로 웹페이지를 수정하는 [동적웹페이지]의 특징이기 때문이다.
    찍힌 29경기수는, 아직 refresh되기전의 1위팀의 경기수이고,
    다 로딩이 되어 동적으로 수정된 것의 1위팀 경기수는 38이다.
    image
    <이러한 동적웹페이지 크롤링이 불가능한 것은 scrapy만 사용한 한계이다.>


Selenium을 이용하여,   scrapy의 동적 웹페이지 스크래핑의 한계 극복하기

현재 띄워놓은 scrapy shell을 [ctrl+d]로 종료하고, 다시 shell을 띄우자.

shell에서 사용했던, response안에는 [정적웹페이지]의 응답정보를 담고 있다. 그러므로, scrapy의 response 대신
selenium에서 제공하는 webdriver모듈을 import한 다음,
[ webdriver모듈 + 크롬드라이버의 절대경로(tab으로 자동완성) ]를 통해, 크롬브라우저를 생성하여 browser 변수에 할당한다.
새로운 크롬브라우저가 실행이 된다.
from selenium import webdriver
browser = webdriver.Chrome("C:/Users/cho/chromedriver.exe")
image

  • response.url 을 통해, shell에 띄운 크롤링할 웹페이지의 url만 얻는다(response의 정보 이용x  .xpath (x))
    image

  • 이제 browser에 .get(  url  )함수를 이용해서, 새롭게 띄워진 브라우저에 크롤링할 웹페이지를 띄우도록 만든다.
    browser.get(response.url)
    image

  • 이제 scrapy의 response.text와 같이,
    현재 browser에 띄워진 웹페이지전체 html소스코드를 String형태로 담기 위해 아래코드를 실행한다.
    html = browser.find_element_by_xpath('//*').get_attribute('outerHTML')
    image
    <브라우저.get()함수 호출 후 – 충분한 시간간격(5초)가 있어야지 – 동적으로 생성된 컨텐츠가 포함된 html코드가 얻어진다>

  • selenium을 통해 얻어진 동적웹사이트의 html코드는 스트링으로 밖에 얻을 수 없다.
    그러므로, String을 –> scrapy의 Selector로 변환하는 과정이 필요하다.
    (1) scrapy의 selector.py의  Selector클래스import해야한다.
    from scrapy.selector import Selector

    (2) Selector클래스의 객체를 생성하고, 인자 text=에다가 String형 html코드 변수 를 넣어준다.
    selector = Selector(text = html)

    이 때, 소문자 selector
    띄워진 html코드를 selector 형태로서 가지고 있게 되므로,
    srcapy의 웹사이트 응답정보 변수이면서, parse()함수의 인자로 들어가는 response와 완전히 동일한 것
    이다.

    (my : response는 웹사이트에 대한 응답으로서, 전체 html코드를 셀렉터형태로 가지고 있었고,
           selenium을 통해 얻은 동적웹페이지의 전체 html코드의 문자열을 –> Selector객체에 담게 되면, response와 같은 놈이 된다!)
    image

  • 이제 browser에 띄워진 웹사이트에서, 크롬개발자도구를 이용하여, 원하는 부분의 xpath를 얻고 selector.xpath로 추가하면 된다.
    (broswer에 띄워진 것을 크롬개발자도구로 선택하여, xpath를 추출한 뒤, 셀렉터에서 얻으니까 한방에 얻어진다..
    맨끝의 td의 인덱싱으로 각각 필요한 정보를 바로 뽑아낼 수 있다.
    대신 한 item(행)만을 추출은 안된다. for문에 들어갈 전체item 리스트는 뒤에서 따라올라가는 식으로 잡아야할듯)

    1위팀의 이름xpath셀렉터 )
    selector.xpath('//*[@id="mainContent"]/div[2]/div[1]/div[3]/div/div/div/table/tbody/tr[1]/td[3]/a/span[3]')
    1위팀의 이름text()추출)
    selector.xpath('//*[@id="mainContent"]/div[2]/div[1]/div[3]/div/div/div/table/tbody/tr[1]/td[3]/a/span[3]/text()')[0].extract()
    1위팀의 경기수text()바로 추출)
    selector.xpath('//*[@id="mainContent"]/div[2]/div[1]/div[3]/div/div/div/table/tbody/tr[1]/td[4]/text()')[0].extract()
    image


여기까지 정리하자면,
원래 spider.py 속의 parse(self, response)함수에서, response변수에 얻어진 <정적컨텐츠가 포함된 웹페이지 셀렉터형태의 html>는 사용하지 않을 것이고, selenium webdriver를 사용하여 해당 url의 웹페이지를 직접 불어들인 뒤, <동적컨텐츠까지 포함되어 로딩된 html코드>를 사용하여 selector객체를 생성하고, 이를 이용해서 웹 스크래핑을 할 것이다.

  • 이제 scrapy shell –> selenium webdriver –> scrapy Selector 객체에 담긴 데이터를 이용해서
    ( 기존 scrapy shell –> 직접 웹페이지띄움 –> scrapy response )
    epl_spider.py의 EPLSpider클래스의 parse()함수를 채워야한다.
    그전에, 먼저 [epl_spider.py]에서 selenium의 webdriver를 사용할 수 있도록 각종 설정을 해줘야한다. 
    (우리가 shell에서 selenium webdriver를 import하고 사용한 것은, xpath를 알아내기 위함이다.
    이 알아낸 것들을 이용해서 ---> 결국에는 [ epl_spider.py ]에서 다 작동시켜야한다.)

    (1)[ epl_spider.py ]에  selenium의 webdriver모듈을 import해준다.
        from selenium import webdriver
    (2) EPLSpider클래스의 생성자 __init__(self): 함수오버라이딩 해야한다.
    (기존 pipelines클래스만 생성자 오버라이딩해서, csv쓰고, 첫행 기입)
    - 먼저 생략되있던 scrapy의 Spider클래스를 초기화해준다.(오버라이딩 하기전에, 기존 내부적으로 자동호출되는 초기화함수코드)
        scrapy.Spider.__init__(self)
    - 이제 spider클래스가 호출될 때 작동할 초기화 부분에,  broswer변수 생성 및 크롬드라이브 호출이 되도록 한다.
        self.browser = webdriver.Chrome("C:\Users\cho\chromedrive")
    image

  • 이제 parse(self, response) : 를 채워보자.
    (1) self.browser변수에 동적 웹페이지를 불러올 것인데, get함수의 인자에는
          기존 scrapy의 response 이용해서, url주소만! 을 얻어온다.
    *** time.sleep(5)를 통해 웹페이지가 동적컨텐츠를 모두 로딩할때까지의 시간을 줘야한다!
        < 크롬broswer를 통해 get으로 웹사이트 다시한번 띄우기 < ---충분한시간필요(5초)-----> html코드를 담기>
    *** import time
        from scrapy.selector import Selector
        < Selector 클래스 및, time모듈을  spider에도 import해줄 것! 강의에서는 안해줬는데, 에러남!>
    (2) 이전에 shell에서 했던 것처럼,  browser변수를 이용해서, 전체 html코드를 문자열으로 불러와 html변수에 담고
         Selector객체를 생성해 html문자열 변수를 인자로 넣어서, selector형태로 담아준다.
    (3) selector에 .xpath(‘ 전체 item의 리스트를 담는 xpath’)를 넣어, for문에서 각 item으로 꺼내쓰도록 한다.
      (이 때, 각 item(행)이 webdriver에서 안찍히더라도, 타고 올라가서 확인할 수 있고, 바로 xpath가 뽑힌다
       주의 할점은, 각 item(행)을 의미하는 <tr>가  xpath에서 tr[1]로부터 차근차근 시작하는 것이 아니라,
       <tr>의 속성중 클래스 이름이 “expandable”이 아닌 것을 리스트로 가져와야한다는 것을 알아챈 뒤,
       /tr[  not (@class= “expandable” ) ] 으로 인덱싱할줄 알아야하는 것
    이다.)
    (4) for문에서는 전체item리스트를 담은 변수(rows)를 인자로 넣고, row로 하나씩 꺼내본다.
        for문안에서, [items.py]에 정의해둔 EPLItem객체를 생성하고,
        객체의 각 필드에 꺼낸 row에 .xpath경로를 추가해서 세세한 text()를 [0].extract()추출한 것을 담아준다.
    (5) spider의 parse함수에서 item객체에 데이터를 넣은 뒤, yield로 반환했다.
    image

다시한번 정리하자면)
EPLSpider클래스에서 다시한번 생성자를 오버라이딩해서, 기본코드 + 크롬브라우저를 띄우도록 했고,
parse함수에서 크롬브라우저를 이용해서  다시한번 해당 웹페이지url을 불러온다. (scrapy에서 기본적으로 shell에서 한번 불러오므로)

  • 이제 크롤링을 하기 위해서, shell은 종료하고, 크롤링 프로젝트 1번째 폴더로 이동해야한다.
    cd epl_crawler

  • 크롤링을 시작한다. –o를 옵션을 통해서 pl.csv로 저장하도록 해보자.
    scrapy crawl PremierLeague -o pl.csv

<시작시 각종에러>
1. chrome driver의 절대경로는 폴더할때 쓰이는 \(W) 가 아니라 반대방향 / (쉬프트옆 문자)로 표시할 것.
2. items.py 의 scrapy.Item를 item 소문자로 잘못적었음.
3. shell에서는 해줬었던 Selector 클래스 및, time모듈 을  spider에도 import해줄 것
import time
from scrapy.selector import Selector

4. 전체리스트의 경로 수정
5. parse함수대로 데이터가 나오지 않는다. 수정하고 싶다면 pipeline을 이용할 것!

image

image

  1. TedKim 2018.04.17 23:15

    해도 해도 안되서 포기하고 다른 인터넷 강의 찾아보려고요..
    어떻게 하셨는지, time 이랑 selector import 해오고,
    rows의 xpath만 수정하고는 다 똑같이 flearning거 복사했는데
    크롬 드라이버도 아래와 같이 패스 지정하고
    self.browser = webdriver.Chrome(executable_path=r"C:\Users\tedkim\chromedriver.exe")
    ㅜㅜ 또 신경써야할게 있을까요 4일동안 보고 이만 놓아줍니다 ㅜ

    • nittaku 2018.04.27 19:41 신고

      시험기간이라... 아예 확인을 못했네요. 손놓은지 제법되었는데, 곧 크롤링 복습하고 답변드릴게요!

    • nittaku 2018.04.27 19:43 신고

      self를 빼고, 그냥 browser 에 대입해줬습니다. 그래두 안되는가요?

      <시작시 각종에러>
      1. chrome driver의 절대경로는 폴더할때 쓰이는 \(W) 가 아니라 반대방향 / (쉬프트옆 문자)로 표시할 것.
      2. items.py 의 scrapy.Item를 item 소문자로 잘못적었음.
      3. shell에서는 해줬었던 Selector 클래스 및, time모듈 을 spider에도 import해줄 것
      import time
      from scrapy.selector import Selector
      4. 전체리스트의 경로 수정
      5. parse함수대로 데이터가 나오지 않는다. 수정하고 싶다면 pipeline을 이용할 것!

      이것들 다시 확인해보셔요

  2. 임성우 2018.07.02 10:26

    안녕하세요~ 궁금한점이 있습니다. 이.. scrapy가 일종의 프레임워크지 않습니까?
    아 그러니까..
    현재 scrapy로 크롤링하여 DB안에 넣는것에는 성공했습니다.
    그리고 php에서 이 DB에 있는 내용을 뿌려주는 것도 가능한데
    문제는 연속성에 있습니다.
    그러니까 처음에 제 생각은 php에서
    $command = escapeshellcmd("scrapy crawl 스파이더이름 ");
    $output = shell_exec($command);
    뭐 이런식으로 가능하지 않나 싶었는데 이게 불가능한 것 같은데....어쨌든
    그래서 위의 PHP코드로 크롤링을 시작하여 DB안에 넣고
    다시 PHP로 크롤링 된 DB안의 내용을 뿌려주는게 가능할까 싶어서 scrapy로 크롤링하였는데요.
    제가 파이썬 개념이 없어서 그러닞 첨부터 잘못됐다는 생각이 듭니다.

    $command = escapeshellcmd("파이썬파일명.py")는 가능한데
    $command = escapeshellcmd("scrapy crawl 스파이더이름 ") 이 실행은 불가능한가요?


  3. irongaea 2018.08.02 11:07

    한 이틀 고생 했지만, 잘 되는군요.
    macOS python 3.7에서도 잘 동작합니다.
    EPL 사이트말고 다른 동적 페이지로 했습니다.
    정말 감사합니다.
    참고 : https://irongaea.github.io

    • nittaku 2018.08.02 11:09 신고

      오 해결하셨군요. 저는 딥러닝공부중이라 손을 놓은상태여서 당장 답변이 불가했는데, 공유감사합니다.

  4. 정재윤 2019.01.17 10:30

    '19 1월 현재 epl 사이에서 매치 테이블을 조회하면 무조건 최근 시즌 내용이 뜨네요
    하위 파라미터까지 다 붙은 아래 주소로 접속해도 18/19 시즌 내용이 나옵니다.
    "https://www.premierleague.com/tables?co=1&se=42&ha=-1"

    크롬드라이버로 띄워봐도 동일합니다.
    이런 경우엔 어떤 방법으로 해당 페이지에 접근할 수 있을까요?

    • nittaku 2019.01.17 17:37 신고

      다른 홈페이지로 연습해보세요. 저걸 한지 꽤 되서요..ㅎ;;



정적 웹페이지를 크롤링하는 것은 scrapy만 사용할 것이다. py27 환경에서 pip install jupyter로 쥬피터 노트북을 설치해놓자.
데이터를 수집할 사이트는 https://www.rottentomatoes.com/top/bestofrt/?year=2015 이다.
영화리스트에서 하나를 선택하게 되면, 영화 상세페이지가 나오게 된다. 그 상세 페이지에서 제목, 평점 등의 정보를 가져오도록 해보자.

정적 웹페이지 Scray로 크롤링 하기

  • Prompt에서  [scrapy 프로젝트]를  생성해줘야한다.
    scrapy startproject rt_crawler
    image
    rt_crawler 라는 폴더가 생성되, 다시 똑같은 제목의 폴더가 있고, scrapy구조가 완성 된 것을 알 수 있다.
    확인은 jupyter해서 하면된다.
    image
    우리는 이 *.py파일들을 수정한 뒤, spider를 실행하여, 최종적으로 스크래핑 및 크롤링을 할 것이다.
  • rt_crawler/
        scrapy.cfg            
        rt_crawler/           # 해당 프로젝트의 Python 모듈
            __init__.py
            items.py          # 프로젝트 Item 파일
            pipelines.py      # 프로젝트 Item pipeline 파일
            settings.py       # 프로젝트 Settings 파일
            spiders/          # Spider 저장 폴더
                __init__.py
                ...
  • items.py파일을 수정하여 [상세페이지]에서 수집할 정보를 담을 [새로운 item클래스]를 정의해준다. 기존에 자동으로 생성되어있는 클래스는 삭제하자.
    import scrapy
    #스크래파이의 Item클래스를 상속받는 클래스 생성
    class RTItem(scrapy.Item):
        #클래스의 객체에다가 정보를 저장할 필드(멤버변수)를 생성해주는데, title은 Field()형태로 저장할 것이다.
        title = scrapy.Field()
        score = scrapy.Field()
        genres = scrapy.Field()
        consensus = scrapy.Field()

  • spiders폴더에 들어가서, text파일을 하나 생성한 뒤 이름을 [rt_spider.py]로 바꾸고, [ spider] 클래스를 새롭게 정의하여, 각종 규칙을 명시하자.
    #기본적으로 scrapy 임폴트
    import scrapy
  • #우리프로젝트의 items.py에 만들어준 [RTItem 클래스] 도 import
    from rt_crawler.items import RTItem

    #scrapy에서 제공하는 Spider클래스를 상속한다.
    class RTSpider(scrapy.Spider):
       
        # 해당 Spider의 이름. 웹 크롤링 및 스크래핑을 실행할 때 여기서 지정한 이름을 사용함.
        name = "RottenTomatoes"
       
        #Spider로 하여금 크롤링하도록 허가한 웹 사이트의 도메인 네임.
        allowed_domains= ["rottentomatoes.com"]
       
        #웹 크롤링의 시작점이 되는 웹 페이지 URL. 해당 웹 페이지에서 출발하여 이어지는 웹 페이지들을 크롤링함
        start_urls=[
            "https://www.rottentomatoes.com/top/bestofrt/?year=2015"
        ]
       
        #start_urls의 웹페이지를, spider가 서버에 요청하게 되고, 이에 대한 응답을 reponse라는 변수에 받는데, 이 reponse를 받아 처리하는 parse()함수를 정의
        def parse(self, response):
            #response로 얻어진 웹사이트의 어떤부분을 스크래핑할지 명시해줘야한다

  • rt_spider.py의 RTSpider클래스에서 parse()함수에 <상세페이지로 가기위한 하이퍼링크url >을 얻기위하여,
    어느 요소를 처리할지를 알기 위한 도구인 [ scrapy shell ][ 크롬 개발자도구 ]를 사용해서 찾아내야한다.
    (1) 쥬피터를 띄우고있는 것 외에 새로운 prompt를 켜서, (py27)환경에서  scrapy shell을 실행시키자.
    activate py27
    scrapy shell “https://www.rottentomatoes.com/top/bestofrt/?year=2015
    (2) 다른 한켠에는, 해당 사이트에서 크롬 개발자도구를 켜서, 하이퍼링크를 담은 제목요소를 가리키는 xpath를 얻는다.
    (3) 복사된 xpath를 scrapy shell에서 response.xpath(‘//*[@id="top_movies_main"]/div/table/tbody/tr[1]/td[3]/a’)를 입력해도 아무것도 안나온다.

<예외처리1>

  • ***크롬 개발자도구의 xpath 추출기능이 만능이 아니라는 것이다.
    *** 이 때, seletor가 얻어지는 xpath를 찾기 위해서, 뒤에서부터 한단계씩 지워서 거슬러 올라가면 된다.
    (4) xpath의 마지막 뒤부분을 table까지 남기니까 selector가 리턴되었다!!!
    response.xpath('//*[@id="top_movies_main"]/div/table')
    image
    (5) 이제 해당xpath에서 필요없는 경로를 삭제하면서 다시 최종적으로 타고 들어가야된다. 개발자 도구상 마우스로 살펴보면, table클래스 밑에 있는 <thead>와 <tbody>는 개별 item들과는 상관이 없는 요소였다. 그래서 xpath상의 tbody를 지운체, 다시 자식을 그대로 타고 가보았더니 최종적으로 원하는 데이터가 나왔다.
    (a= 하이퍼링크요소, 까지 다시 타고 들어가야한다!)
    response.xpath('//*[@id="top_movies_main"]/div/table/tr[1]/td[3]/a')
    image
    (6)이제, 크롬개발자 도구상에서 보면, 하이퍼링크요소 <a ~ > </a> href라는 속성값하이퍼링크 url이다.
    저 주소를 가져오기 위해서 xpath 마지막에[  /@href  ]를 붙혀 주면, a요소의 자식속성 href를 Selector에 가져오게 된다.
    바로 뽑아내기 위해서, href가 만약 리스트라면 [0]첫번째를 가져오고, extract()로 selector가 아닌 실제값을 추출하게 한다.
    response.xpath('//*[@id="top_movies_main"]/div/table/tr[1]/td[3]/a/@href')[0].extract()
    image
    이 때, 맨마지막에 .encode(‘utf-8’)로 인코딩해주면, 앞에 달린 u가 사라진다.
    response.xpath('//*[@id="top_movies_main"]/div/table/tr[1]/td[3]/a/@href')[0].extract().encode('utf-8')
    print에 인코딩 없이 출력해도, 마찬가지로 u가 사라진다.(print에는 인코딩 기능까지)
    실제 데이터를 추출해보면, u는 사라지므로 일단 남겨두자
  • 이제 한 item의 상세 페이지의 서브url이 아니라, 전체 도메인까지 들어간 Full url이 필요하다.
    서브url을 url변수에 저장하고, response.urljoin()함수를 이용해서, 자동으로 전체도메인에 서브도메인을 붙히자.
    url = response.xpath('//*[@id="top_movies_main"]/div/table/tr[1]/td[3]/a/@href')[0].extract()
    response.urljoin(url)
    image
  • 이제 하나의 item에 대한 a요소로 찾은 상세페이지url이 아니라 전체 item들에 대한 url들이 모두 필요하다.
    html구조를 거슬러 올라가서 찾아보자.
    a요소 > td > tr까지 올라가보니까,  tr이 반복되면서 , 리스트의 하나의item들이 각각 반복되는 것을 확인할 수 있다.
    image
    즉, <tr> : 리스트의 각 하나의 item,   3번째<td> :  <a>요소에 제목과, 하이퍼링크속 Suburl을 포함한다.
  •  모든 tr요소들을 얻도록 xpath를 고쳐야한다.
    즉, item을 의미하는 <tr>요소의 인덱싱을 제거한 뒤, response.xpath(‘’)에다가 넣어서 셀렉터리스트로 반환받는다.
    -> for문인자에 넣어서 각 item을 의미하는 tr의 셀렉터하나씩 꺼내도록한다.
    –> 각각의 성분마다 .xpath(‘./’)을 이용해서 /@href 속성을 얻는 xpath경로를 셀렉터에 추가한 뒤 href변수(한 item의 href까지의 셀럭터)에 받는다.
    ->  href변수에 담긴 href속성 중 첫번째(서브url)을 [0].extract()한 것을  response.urljoin()을 통해  전체도메인에 붙혀서 url변수에 저장한다.
    -> 확인을 위해 print(url)해준다.

  • In [33]: for tr in response.xpath('//*[@id="top_movies_main"]/div/table/tr'):
         ...:     href = tr.xpath('./td[3]/a/@href')
         ...:     url = response.urljoin(href[0].extract())
         ...:     print(url)
    image
    리스트의 100개의 item에 대해, 하이퍼링크 url을 얻었다.
    얻은 100개의 url을 가지고, 각 웹페이지를 요청하기 위해서, scrapy에서 제공하는 request함수를 사용할 것이다.

  • 이제 100개의 리스트를 얻는 for문 부분을 복사해서 [rt_spider.py]의 response를 처리할 수 있는 parse()함수로 가져온다.
    마지막의 print만 삭제 한 뒤,  yield라는 키워드를 사용하여 scrapy의 함수 scrapy.Request함수를 사용하게 된다.
    scrapy.Request(,)의 인자로는, 각 item의 하이퍼링크 full url을 넣어주고,  또다른인자 callback=에다가는,
    url을 통해 불러온 페이지의 html소스코드를 어떻게 스크래핑할지 정해줄, 사용자 정의 함수를 넣어준다.
    def parse(self, response):
         #response로 얻어진 웹사이트의 어떤부분을 스크래핑할지 명시해줘야한다.
        
         for tr in response.xpath('//*[@id="top_movies_main"]/div/table/tr'):
             href = tr.xpath('./td[3]/a/@href')
             url = response.urljoin(href[0].extract())
             yield scrapy.Request(url, callback = self.parse_page_contents)

  • 이제 scrapy.Request()함수의 callback인자에 넣어준 parse_page_contents()함수를 정의해보자.
    리스트의 각 url을 통해 불러온 페이지의 html소스코드를 어떻게 스크래핑할지 정해줄 것이다.
    이 때, 인자로 들어가는 response는 scrapy.Request()의 각 상세페이지 하이퍼링크url을 통해 불러온 응답(html소스코드)을 의미한다.
    parse()가 start_urls에서 얻은 웹사이트의 response를, xpath를 통해 어떻게 스크래핑할지 정의하였는데,
    parse()속의 parse_page_contents()는 parse()함수의 마지막 Request(url, callback=)의 url에서 얻은 response를 어떻게스크래핑할지 정의하는 함수다. 그러므로, 파싱할 상세페이지를 shell과 크롬개발자도구로 먼저 살펴본 뒤, parse함수를 정의해준다.

    (1) 현재 실행된 scrapy shell을 [ctrl+d]를 눌러서 종료해준다.
    (2) 하나의 item 상세페이지url을 복사한 다음, scrapy shell을 다시 작동시키자.
    scrapy shell https://www.rottentomatoes.com/m/mad_max_fury_road
    (3) 이 상세페이지에서는 영화제목/평점/장르/총평의 정보를 가져올 것이므로, 크롬개발자도구를 이용해서 각각의 xpath를 알아야한다

<하이퍼링크를 타고들어가서, 상세페이지의 item의 xpath를 통해, 필요한 요소들 뽑아오는 과정>

  • (1)상세 페이지의 제목을 클릭xpath를 카피해, response.xpath()를 통해, selector를 얻어냈다면,
    (2) /text()를 xpath에 더해서  제목을 가지는 셀렉터인지 확인하고,
    (3) [0].extract()를 통해 텍스트만 뽑아내보자.
    response.xpath('//*[@id="heroImageContainer"]/a/h1/text()')[0].extract()
    image
    (4) 이제 추출한 텍스트 앞에 있는 <\n과 공백>을 제거하기 위해서, 문자열에 대해서, .strip()함수를 적용시키면된다.
    response.xpath('//*[@id="heroImageContainer"]/a/h1/text()')[0].extract().strip()
    image
  • 비슷한 방법으로 영화 평점도 뽑아오자.

<예외처리2>

  • 1개의 평점을 클릭한 뒤, xpath를 통해, 셀렉터를 뽑았는데 2개의 셀렉터가 나왔다.(아마도 크롬의 xpath뽑는 기준이 완벽치않음)
    이 때는, 우리가 원하는 text()만 뽑은 다음, 결과물에 인덱싱[0]을 통해서,  첫번째것만 가져오는 식으로 한다. 그리고 extract()한다.
    (기존에 항상 [0].extract()한 것과 동일함)
    response.xpath('//*[@id="tomato_meter_link"]/span[2]/span/text()')[0].extract()
    image
    이렇게 scrapy shell과 크롬개발자도구로 [ 상세페이지의 원하는 정보를 스크래핑하는 코드 ]를 다 찾아낼 수 있다.
    --> 여기까지 찾아냈으면, 앞서 정의했던 parse_page_contents()함수에다가 넣어줘야한다.
  • 이제 상세페이지의 각 필요한 정보들을 스크래핑하는 코드를
    for문을 돌면서 각 웹페이지를 불러오는 곳(Request)의 callback인자를 통해 호출되는 parse_page_contents()함수에 넣는데,
    처음 [items.py]에 정의한 양식대로, 객체를 만들어서, 거기다 담아야한다.(처음에 items.py의 RTItem클래스를 import했었다)

<예외처리 3> 장르 같은 경우, 리스트로 나온다.

  • 장르 같은 경우, xpath로 셀렉터를 뽑아보면, < 공백가져서 각각 strip()필요한 2개 리스트 > 가 나와, 리스트형식으로 가져와야한다.
    (1) xpath를 통해 셀렉터 뽑고, /text()를 통해 확인한다.
    (2) 줄바꿈과 공백을 포함하더라도, 리스트로 나오기 때문에, [0].extract.strip()을 하면 안된다. (리스트는 .strip()도 안된다)
    (3) extract()를 먼저하여 셀렉터 리스트 –> data리스트만 따로 변수에 담는다.
    genres_list = response.xpath('//*[@id="mainColumn"]/section[3]/div/div[2]/ul/li[2]/div[2]/a/text()').extract()
    (4) 결국 parse_page_contents()함수에서 객체 속 하나의 필드에 저장해야하므로, 리스트를   ‘’구분자.join( 리스트)함수로 리스트를 합치면서,
        각각에 strip()이 적용시킨다.
    객체의 필드 = ''.join(genres_list).strip()
    spider클래스의 parse()함수에서 나온 리스트형data는, join없이 그냥 리스트를 list변수에 담아두고, 변수에 map()으로 줄바꿈+공백만 삭제
    ->  뒤 pipelines에서 join해줄 것이다.

    image

  • 총평(census)는 일단 생략하자. 그리고 마지막은 yield를 통해 채워진 객체 item을 반환해주자.

  • def parse_page_contents(self, response):
            item = RTItem() #임폴트한 RTItem의 객체 생성
            #RTItem클래스에 정의한, 객체에서 쓸, 필드(멤버변수)들은 열 인덱싱처럼 뽑아낸다.
            item["title"] = response.xpath('//*[@id="heroImageContainer"]/a/h1/text()')[0].extract().strip()
            item["score"] = response.xpath('//*[@id="tomato_meter_link"]/span[2]/span/text()')[0].extract()

                genres_list = response.xpath('//*[@id="mainColumn"]/section[3]/div/div[2]/ul/li[2]/div[2]/a/text()').extract()
                item["genres"] = genres_list = map(unicode.strip, genres_list)

            yield item


    여기까지가 [items.py] 와 [rt_spider.py] 정의가 끝났다.
    scrapy shell을 종료시키자.

  • 이제 우리가 제작한 spider실행시켜야한다.
    (1) (py27)환경에서, 우리가 만든 프로젝트이름인 rt_crawler 폴더로 먼저 간다.
    cd rt_crawler
    (2) (py27) C:\Users\cho\Web_da\rt_crawler> 상태에서 크롤러를 실행시키는 명령어에 정의한 크롤러이름을 넣어 실행시킨다.
    ( 크롤러이름은,, 프로젝트명폴더/프로젝트명폴더/spiders에 만들어준 [rt_spider.py]name = “RottenTomatoes” 를 넣어준다)
       scrapy crawl RottenTomatoes
    (3) 만약 csv파일로 저장하고 싶다면 scrapy crawl RottenTomatoes -o rt.csv
    각종 indexError가 나서, 100개 다 스크래핑 못해오더라.
    image
    결과물을
    프로젝트 폴더에서 확인해보면, RTItem에는 있는 필드지만, 스크래핑을 생략한 필드에 대해서는 , , 으로 비어서 나온다.
    image

<예외처리 4>
한글로 주석달아서, 파이썬이 한글을 못읽는 문제 :

SyntaxError: Non-ASCII Character 관련된 에러라고 부릅니다. 코드 내에 한글을 파이썬이 읽어들이지 못해서 발생되는 에러
해당 *.py의 첫번째 줄or 두번째 줄에 : # -*- coding: utf-8 -*-
주석을 추가해준다.
image
<예외처리 5>
나같은 경우, 장르 리스트의 2번째 성분 앞에 있는 < \n    공백  >이 안사라졌다.
구글링 결과, list에 \n이라는 유니코드를 먼저 없어줘야한다.
genres_list = map(unicode.strip, genres_list) 를 통해서, \n을 먼저 사라지게 한 뒤,
item["genres"] = ', '.join(genres_list).strip()  를 통해, 구분자로서 콤마(,)를 넣고, 공백도 제거하자.
--> join은,, 나중에 pipelinse에서 할 것이다.


  • 스크래핑한 csv결과물을 보니,  스크래핑 순서가 역순으로 되어있고, 리스트인 genres가 2개 이상인 경우 “”쌍따옴표를 가져
    식별하기 어려운 형태로 저장되었다.
    image


웹스크래핑을 통해 얻어진 데이터를, 어떻게 가공하고, 외부파일로 저장할지 구체적으로 명시하기 위한 item pipelines

  • 프로젝트 폴더> 프로젝트명 폴더 > [pipelines.py]를 열자.
    (1)기존 클래스를 제거하고, csv모듈을 import한다.
    import csv
    (2) [RTPipeline]클래스를 정의한다.(object를 상속한다)
    (3) 생성자함수( 자바에서는 클래스의 객체생성시, 필드들 초기화)를 정의해주는데, 생성자안에
        import한 csv모듈을 이용하여, 매 행을 csv파일을 쓰도록 정의해줄 것이다.

  • #csv모듈 임폴트
    import csv

    #object를 상속하는 RT크롤러의 pipelines클래스 정의
    class RTPipelines(object):
        
         def __init__(self):
             #생성자에는 self를 이용해 변수를 만들고, csv모듈의 writer함수를 이용해서, csv파일을 열고 쓸 것이다.(없다면 생성)
             self.csvwriter = csv.writer(open("rt_movies_new.csv", "w"))
            
             #이 순서대로 csv파일의 열들을 채워넣는겠다는 의미로서, 열을 직접 입력해준다.
             self.csvwriter.writerrow( ["title", "score", "genres"] )

    image

  • 이제 새로운 함수 process_item()함수를 정의할 것인데, 인자로서는 self외에 item(객체)spider가 들어간다.
    spider를 이용해 수집한 item들을 어떻게 처리할지를 명시하게 된다.
    파이썬 리스트를 하나 생성한 뒤에, 각 item객체의 필드들 append할 것이다.
    genres는 리스트이므로 |문자를 구분자로 하여 .join으로 각각을 연결해준다.
    다음에는 self.csvwriter.writerow를 통해 순서대로 쌓아둔 리스트(row)를 각 행에 밀어넣는다.
    이제 객체를 사용한 item은 returnd을 해줘야한다.

  • def process_item(self, item, spider):
             row = []
             row.append(item["title"])
             row.append(item["score"])
             row.append('|'.join(item["genres"]))
             self.csvwriter.writerow( row )
            return item

    image

  • 이제 정의한 item pipelines크롤링시 사용하도록, settings에서 설정을 해줘야한다.
    (1) [settings.py]를 열고,  # ITEM_PIPELINES 부분을 찾아 주석을 해제한 뒤,
        pipelines.py에 우리가 정의한 클래스(RTPipelines)를 명시해준 뒤 저장한다.

    image

  • setting에서 크롤링시 자동으로 csv파일을 생성하여 쓰기 때문에,
    scrapy crawl RottenTomatoes 뒤에 –o rt.csv를 적을 필요가 없다.
    image
    ***나는 미리, 리스트를 join해버려서,,, 다시 join시키는 꼴이므로,, 각 글자마자.. |가 찍힌다.
    *** pipelines에서 join함수를 사용할거면, 
         rt_spider.py에서, 각 필드에 담을 때는,, join없이, 그냥 리스트를 유지하고, \n등 유니코드만 제거해준다.
    image

    image
    my)  spider에서 객체에 xpath를 통해 추출해서 담을 때는, 리스트는 유지하고 join쓰지말고, map()으로 유니코드만 제거 + yield로 item리턴
           pipelines에서 객체의 필드를 꺼내 쓸때는, 리스트의 경우 구분자와 join을 써서 각 항목 보기좋게 구분 + return으로 item리턴

최종 결과물

image

  1. 김학건 2018.04.16 09:15

    genres 가져올 때, 공백 어떻게 제거하셨나요?
    for문으러 strip 하고 리스트에 넣었는데 아예 뜨지를 않네요

    • 김학건 2018.04.16 09:17

      아 map(unicode.strip, genres_list)로 해주셨었네요. ㅎ

  2. irongaea 2018.07.31 21:03

    좋은 정보 잘 보고 갑니다. 2018년 7월 1위는 black panther군요.
    python3.7 에서
    item['genres'] = list(map(str.strip, genres_list) 입니다. 한참 찾았습니다.

Scrapy의 기본구조

  • Spider는 어떤 웹 사이트들을 어떠한 규칙에 의거하여 크롤링할 것인지 명시하고, 각각의 웹 페이지의 어떤 부분을 스크래핑할 것인지 등을 일괄적으로 명시하는 클래스이다. 즉, 웹크롤링과 웹스크래핑 규칙을 설정하는 핵심요소
  • Spider 상에 웹 페이지 상의 어느 부분을 스크래핑할 것인지 명시하고자 할 때, 특정 HTML 요소를 간편하게 선택할 수 있도록 하는 메커니즘을 scrapy에서는 Selector 클래스로 구현하였다.
    Selector를 사용하면 CSS 선택자를 직접 사용하여 특정 HTML 요소를 선택할 수도 있으나, HTML 상에서의 특정 요소를 선택하는 데 특화된 언어인 'XPath'를 사용하는 것이 더 권장된다. HTML과 CSS의 기초를 잘 알고 있다면, XPath는 몇 개의 예제를 따라서 작성하는 것만으로 금방 학습할 수 있습니다. 만약 XPath에 대해 좀 더 자세하게 배우고 싶다면, 다음의 링크를 참조하시길 바란다.
    http://www.w3schools.com/xsl/xpath_intro.asp
  • Item은 scrapy에서 기본 제공하는 자료구조 클래스입니다. 새로운 Item 클래스를 정의하고 여기에 우리가 수집하고자 하는 정보들을 명시하면, Spider 상에서 실제 스크래핑을 수행한 결과물을, 파일형태로 저장할 때, item을 매개로 사용해서, 간편하게 관리할 수 있다.
  • Item pipeline 클래스를 새로 정의하고 여기에 각 Item들을 어떻게 처리할 것인지 명시하면, 해당 규칙에 의거하여 데이터를 가공하거나 혹은 외부 파일로 간편하게 저장할 수 있다.
  • Spider나 Item pipeline 등이 어떻게 동작하도록 할 지에 대한 세부적인 설정 사항Settings 상에 명시한다
    거의 모든 웹 사이트에서는 크롤링을 수행하는 크롤러 봇들의 행동을 제한하고자 robots.txt라는 파일을 게시한다.
    https://www.flearning.net/robots.txt 처럼, robots.txt에는, 크롤러들이 ~어떻게 행동하라~ 라고 명시되어있다.
    예를 들어, 우리가 제작한 Spider가 robots.txt 파일에 명시된 규칙을 따를 것인지, 혹은 무시할 것인지 등을 Settings 상에서 설정할 수도 있습니다. 서버에서 차단당하기 전에, 완급조절도 Settings상에서 설정가능하다.



Scrapy shell과 크롬 개발자 도구를 사용하여 웹 스크래핑 체험하기

1. Anacond Prompt 에서 activate py27로 파이썬 2.7가상환경을 활성화 시킨 상태로 만들자.(scrapy는 py3버전을 지원하지않는다)
activate py27

2. 지정한 웹 페이지를 스크래핑한 결과를 인터랙티브하게 확인할 수 있도록 하는 기능인 scrapy shell을 실행해보자.
scrapy shell http://gall.dcinside.com/board/lists/?id=oriental_medicine

image

3. 웹사이트의 제목을 가져오기 위해서,  페이지 소스보기를 눌러서 어떤 계층을 타고내려왔는지 찾아야하는데,
   크롬에서 제공하는 개발자 도구[F12]를 사용해서 더 간편하게 할 수 있다.
   개발자도구에서 [Elements]를 클릭하면 html소스를 볼 수 있고,  [좌측상단 마우스]아이콘 클릭 후 가져올 부분을 클릭하면,
   html 코드가 하이라이트로 표시된다.
image
이 상태에서 하이라이트된 코드를 [우클릭 ] > copy > [ xpath copy]를 선택하여 해당 html코드를 xpath형태로 복사해오게 된다.

4. 다시 scrapy shell 로 돌아와서 , response.xpath(‘’) 함수를 미리 쳐놓고, 작은따옴표 사이에다가 복사한 xpath를 붙혀넣는다.
   이 때, response라는 것은, scrapy shell작동시 적은 url에 웹페이지 요청을 보낸 것에 대한 응답을 담고 있는 변수이다.
    특정요소의 xpath를 xpath함수의 인자로 넣으면, xpath에 부합하는 해당요소를 나타내는 selector 객체를 리턴받을 수 있다.
   ( 디씨인사이드는 js나 ajax로 만든 동적웹페이지라서,,  selector 반환이 안된다. 그래서 stackoverflow로 바꿨다.)
   이 때, data= 이후부분의 정보를 긁어오는 것이다.
image
<Xpath>
xpath(‘’)안에 들어가는 xpath인자에서
// 는 자손요소들을 모두 살펴본다(가장 상위의 html의 )
/ 는 /앞부분의 직계자식 요소만 살펴보는데, /뒷부분의 조건에 맞는 놈을 찾는다.
* 는 모든 종류의 태그를 찾는다.
[@id =”” ] id값 조건을 명시하는 부분이다. 클래스라면 @class=”” 형식으로 준다.
[1] 대괄호는 인덱싱이다. 주의할 점은 xpath안의 html인덱스는 정수 1부터 시작한다!!

5. xpath인자의 마지막에   /text()를 붙히고 xpath함수를 호출하면, 텍스트만 뽑아내서 selector에 담긴
  그 뒤에 extract()함수까지 적용하면 Selector에  담긴 텍스트를 추출해서 표시한다.
response.xpath('//*[@id="question-summary-49154165"]/div[2]/h3/a/text()').extract()
image
원래 selector에는 리스트형식으로 담긴다. 여기서는 제목 1개부분만 추출하였으므로, extract() 추출한 리스트 중 첫번째[0]을 호출한 것과 동일하다.
response.xpath('//*[@id="question-summary-49154165"]/div[2]/h3/a/text()').extract()
response.xpath('//*[@id="question-summary-49154165"]/div[2]/h3/a/text()').extract()[0]

image


6.  이제 제목을 하나하나 가져올 것이 아니라, 전체 리스트로 가져올 방법을 연구해보자.
    개발자 도구의 하이라이트 부분의 더 상위부모방향으로, 마우스로 타고 올라가다 보면, 제목의 위치에서, 하나의 item전체를 정의해주는 <div 요소 class가 있을 것이다. 그것을 기준으로 닫아보면, 각 제목들이 부여되 된 것을 확인할 수 있다.
   나의 경우, question-summary narrow라는 div요소가 각 질문item들을 정의해주었다.
image

7. 그러면, 처음에 제목을 가진 xpath로 돌아가서, /text()부분을 제거하고, 다시 살펴보자.
//*[@id="question-summary-49154165"]/div[2]/h3/a/
이제  뒷부분부터 단위를 줄여가면서 response.xpath()를 호출하여 data='부분에 해당 div요소(question-summary narrow클래스)가 걸릴 때까지 줄여가보자. 나같은 경우, 뒷부분에서부터 줄여서 갈 수 있는 div의 위치가 div[2]까지 밖에 없다.
response.xpath('//*[@id="question-summary-49154165"]/div[2] ‘)
image


8. 이제 내가 선택한 제목은 2번째글의 제목이므로, div[2]로 인덱싱 되어있다. 인덱싱부분을 지우고 .xpath()를 호출하여,
   전체 질문item 대한 셀렉터리스트를 가져오자. 그리고 가져온 selector리스트를  파이썬 리스트 titles에다가 저장하자.
( 나는ㅠㅠ xpath 자체가,, class 뿐만아아니라 id에 글번호가 표시되어있고, xpath 카피시,, id로 검색되게된다.)
(이 때, 사이트를 바꾸고, id대신 class로 검색하게 했더니—> 모든 질문item에 대한 글 제목까지 한꺼번에 나왔다.)
scrapy shell “https://stackoverflow.com/questions
image
response.xpath('//*[@class="question-summary"]/div[2]/h3/a')
titles = response.xpath('//*[@class="question-summary"]/div[2]/h3/a')


9. titles에 인덱싱을 해서 첫번째를 한번 뽑아보자.
title = titles[0]
title
image

10. 이제 원래는 titles리스트는 각 item전체의 리스트고, 진짜 title까지 내려가줘야한다
     나는 id대신 class로 검색하고, 자식요소들 다 가지고 있으니까, 바로 각 item들의 제목 리스트가 되었다.
    원래대로라면 명시하는 방법은 .xpath(‘./’)함수로 ( 맨처음 글제목을 긁어올 때 들어간)자식요소를 줄 수 있다. 나는 맨 마지막 /text()를 주면 된다.
    이 때,  쩜/로 기존 xpath에 이어줘야한다.
    title.xpath('./text()')
image
extract()로 data(텍스트)부분만 추출해보자.
title.xpath('./text()').extract()
image


11. 이제 반복문을 통해 첫번째만이 아니라. titles 전체에서 제목만 뽑아내자

for title in titles :
    ...:     print title.xpath('./text()'
    ...:     ).extract()
image


글 1개의 제목 요소 찾아서 /text() 로 확인 –>xpath뒷부분 제거하여 item 1개의 div요소 –> 인덱싱을 제거하여 전체 item selector 추출 –>  리스트  titles에 담기-> for문을 통해 각 글 1개의 제목까지 자식요소 복구 후  .xpath( './ 자식요소 / text() ' ).extract()로 다뽑기 or []인덱싱하기

  1. 2018.08.01 22:50

    비밀댓글입니다

    • nittaku 2018.08.02 11:04 신고

      다른 공부중이라 학습하고 확인한 뒤 댓글드릴게요...

scrapy사용을 위한 파이썬 2(2.7)버전 설치하기


scrapy는 파이썬3에서는 제공되지 않으므로, 웹 스크래핑 & 크롤링시에는 파이썬 2.7를 설치해서 사용해야한다.

1. Anaconda Prompt를 켜고, conda명령어로 가상의 파이썬툴 환경을 구성할 수 있다. 여기에 python 2.7을 설치해준다.
   py27이라는 가상환경이 생성된다.
conda create –n py27 python=2.7
image

2. 만들어놓은, py27이라는 가상의 파이썬툴을 activate시켜야한다. 아래 명령어를 실행하면, (py27)환경에서 파이썬이 준비되어있게 된다.
activate py27
image

3. 파이썬 버전을 확인해보자.
python --version
image

4. 만약 웹스크롤링 & 스크래핑 작업이 끝났으면, deactivate 시켜서 다시 python3환경 (base)로 돌아와야한다.
deactivate
image



scrapy 설치를 위해, 추가 파이썬 라이브러리(lxml + pypiwin32 + c++compiler) 설치하기

1. https://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml 사이트에 들어간 뒤, cp27-cp27- 64비트를 찾아서 다운로드한다.

image
.whl 확장자는 수동설치 파일이다. 파이썬 2.7버전이기 때문에 cp2727을 받은 받는 것이다.

2. 다운받은 whl파일을, 설치시 편의를 위해, 홈폴더인C:\Users\cho 에 갖다놓는다.

3. 이제 Prompt에서, (py27)환경을 activate된 상태에서, pip install  + 파일명을 통해 인스톨한다. 이때, tab을 이용해서 자동완성 시키자.
image


4. 윈도우에서 필요로하는, pypiwin32를 설치해야한다. 다운 받은 whl파일에 대한 라이브러리 설치를 한다.
pip install pypiwin32


5. Microsoft Visual C++ complier for python 2.7 를 다운받고 설치해주자.
http://www.microsoft.com/en-us/download/details.aspx?id=44266


Scrapy 설치

1. prompt에서 pip install Scrapy를 쳐서 설치한다.
image


Selenium 설치

1. prompt에서 pip install selenium 를 쳐서 설치한다.
image

2. selenium은 자동화 브라우저 라이브러리이다. 그 브라우저를 사용하기 위한 드라이버 프로그램이 필요로한다.
   크롬에서 제공하는 크롬드라이버를 windows용 가장 최근버전을 다운 받자.
https://sites.google.com/a/chromium.org/chromedriver/downloads
image
압축을 풀고, 드라이버파일을, 홈폴더로 옴긴다.
Selenium을 사용할 때 마다, 드라이버 실행파일을 호출해야한다.


Scrapy 테스트하기

1. (py27)환경을 activate 한 상태에서,  scrapy shell “웹사이트 주소” 형식으로 호출해보자.
scrapy shell https://www.flearning.net/courses/6
image
웹 페이지를 대상으로 scrapy에서 shell형태의 또다른 인터렉티브 프로그램을 제공한다.
image

2.  shell에다가 response.text 명령어를 입력하면, html소스코드를 가져오는 것을 확인할 수 있다.
image

3. 확인이 끝나면 exit()명령어로 나오면 된다.


Selenium 테스트하기

1. 먼저 (py27)환경에서도 ipython을 새로 설치해야한다. (그렇지 않으면 2.7상태에서 ipython 치면 3.6버전으로 인식된다)
conda install notebook ipykernel
image

2.(py27)환경에서 ipython으로 진입한다

image

3. selenium이라는 모듈에서, webdriver를 import한다.
from selenium import webdriver

4. webdriver라이브러리를 이용해서, 크롬드라이버를 실행시키고, 그것을 변수 browser에 저장하자. 새로운 크롬 브라우저가 뜨는 것을 알 수 있다
browser = webdriver.Chrome("C:/Users/Cho/chromedriver.exe")
image


5. 자동화 브라우저를 이용해서, 웹페이지를 띄울 수 있다.
browser.get(http://asdfkakd.com)
image

6. 다 사용하고 나면 끌 수 있다.
browser.quit()

웹 크롤링과 웹 스크래핑

웹  스크래핑(web scraping) : 웹 사이트 상에서 원하는 부분에 위치한 정보를 컴퓨터로 하여금 자동으로 추출하여 수집하는 기술

웹 크롤링(web crawling) : 자동화 봇(bot)인 웹 크롤러가 정해진 규칙에 따라 복수 개의 웹 페이지를 브라우징 하는 행위


image
링크를 따라 돌면서, 연결된 페이지를 가져오는 과정 : 웹 크롤링
image
웹 크롤러가 가져오는 하나의 웹 페이지가 있을 때,  추출하길 원하는 항목의 위치를 지정해서, 데이터로 가져오는 것 : 웹 스크래핑



웹 크롤링 및 스크래핑을 위한 Python 라이브러리 : Scrapy

image
웹사이트를 크롤링 및 스크래핑을 통해 정보를 추출하고, 이를 데이터셋의 형태로 저장하는데 특화된 라이브러리

*문제 : 기초적인 기능만 사용하면, 보고있는 화면을 그대로 스크래핑 할 수 없다. ex> 동적 웹 페이지, 요청시 로그인 정보 함께 보내는 웹 페이지
(ex1) https://www.premierleague.com/tables?co=1&se=42&mw=-1&ha=-1 사이트의 경우
처음에는, 기본적인 정적 웹 페이지(html/css로만 구성)을 띄운 다음, 곧바로 사용자 요청에 의해 드랍박스대로 서버에 요청해서 동적으로 웹페이지가 바뀐다. 만약 여기서 Scrapy만 사용한다면, 처음 잠시 띄워진 정적웹페이지만을 가져온다는 단점이 있다.

(ex2) 로그인 정보를 한꺼번에 보내야하는 웹 페이지의 경우이다.  회원에게 제공되는 웹페이지를 보고 싶을 때, 현재 회원정보로 로그인 되어있다는 정보를 함께 보내야한다. 이것을 전문용어로 쿠키라 한다.
로그인한 상태에서는 메뉴와 강의듣기가 달라진다.(로그인 전 에는 로그인하라고 페이지가 뜬다)
이것은, 요청시, 서버에 쿠키를 같이 보냈기 때문이다.
만약 여기서 Scrapy만 사용한다면, url에 쿠키를 같이 보낼 수 없다.


파이썬 웹브라우저 자동화 라이브러리 : Selenium

image

Selenium에서 제공하는 webdriver 모듈을 사용하여, 동적 웹페이지나 로그인정보를 담아 서버에 요청하는 작업을 할 수 있게 한다.
Scrapy 기초기능의 단점을 보완한다.(고급기능까지 쓰면 되지만, html/css/js의 이해도가 필요하다)

  1. 브로콜리리 2020.07.29 15:51 신고

    좋은정보 감사합니다.

+ Recent posts