• [티스토리 게시글 추천 시스템 만들기] #5 Scrapy로 스크랩하기

    2021. 4. 20. 20:12

    by. 위지원

    2021.03.24 - [✎ 21.上/Data] - [티스토리 게시글 추천 시스템 만들기] #1 계획

    2021.03.24 - [✎ 21.上/Data] - [티스토리 게시글 추천 시스템 만들기] #2 크롤링해서 HDFS에 Parquet으로 저장

    2021.03.25 - [✎ 21.上/Data] - [티스토리 게시글 추천 시스템 만들기] #3 클롤링 데이터 TF-IDF 계산하기

    2021.03.26 - [✎ 21.上/Data] - [티스토리 게시글 추천 시스템 만들기] #4 worldCloud 만들기

     

     

    S3에 저장해서 해보려고 검색하다가 Scrapy를 알게되었다.

    파이썬에서는 아름다운 수프 말고 Scrapy를 이용해서도 웹 크롤링이 가능하다.

     

    Spiders

    Spiders are classes which define how a certain site (or a group of sites) will be scraped, including how to perform the crawl (i.e. follow links) and how to extract structured data from their pages (i.e. scraping items. (docs.scrapy.org/en/latest/topics/spiders.html)

     

    1. BeautifulSoup VS Scrapy

     

    1) BeautifulSoup

    • 라이브러리
    • 직접 웹사이트를 크롤링하는게 아니라 requests 등을 이용해 html 소스를 먼저 가져와야함

    2) Scrapy

    • 프레임 워크
    • spider를 작성해서 크롤링
    • xpath를 이용해 복잡한 html 파싱 가능
    라이브러리 vs 프레임워크 : 주도권이 누구에게 있는가의 차이, 프레임워크는 프레임워크가 가지고 있으며 라이브러리는 사용자가 가지고있음

     

     

    2. scrapy 다운로드

    Scrapy는 아래에서 다운이 가능하다.

    pip install scrapy
     

    Scrapy | A Fast and Powerful Scraping and Web Crawling Framework

    Portable, Python written in Python and runs on Linux, Windows, Mac and BSD

    scrapy.org

     

    2. 프로젝트 생성

    아래 명령어를 이용하면 다음과 같은 구조로 프로젝트 디렉토리가 생성된다. 

    scrapy startproject [project_name]

     

    각각 파일의 용도는

    • items.py: 크롤링해오는 데이터를 class로 받아옴
    • middlewares.py: 미들웨어 설정용인데 검색해보니 셀레늄이랑 연동할 때 사용하는 것 같다.
    • pipelines.py: 크롤링해 온 후 데이터를 처리 
    • settings.py: 말그대로 설정파일
    • spiders: 크롤링할 데이터를 코딩

     

    3. Items.py 작성

    가지고 올 element를 작성한다.

    나는 총 5가지를 가지고오기로 결정했다.

        1. 카테고리

        2. 작성 시간

        3. 제목

        4. 본문

        5. 태그

    import scrapy
    
    
    class TistoryItem(scrapy.Item):
        category = scrapy.Field() 
        title = scrapy.Field()
        date = scrapy.Field()
        contents = scrapy.Field()
        tags = scrapy.Field()
        pass

     

    2.  Spider 작성

    나는 xPath를 이용했다. (css를 이용할 수도 있다.)

    스크랩할 웹사이트에서 개발자도구를 키고 해당 엘리먼트를 클릭하면 Copy xPath를 할 수 있다.

    데이터를 스크랩해오는 코드를 작성한다. 

    • text()는 자식 태그를 제외하고 현재 태그에서 text만 가져온다.
    # -*- coding: utf-8 -*-
    import scrapy
    from tistory_.items import TistoryItem
    
    
    class TistorySpider(scrapy.Spider):
        name = 'tistoryCralwer'
    
        def start_requests(self):
            urls = [''] * 600
            for idx in range(0, 600):
                urls[idx] = 'https://weejw.tistory.com/%d' % idx
    
            for url in urls:
                try:
                    yield scrapy.Request(url=url, callback=self.parse)
                except ValueError:
                    continue
    
        def parse(self, response, **kwargs):
            items = TistoryItem()
            need_path = {
                'category': 'div[1]/div[2]/p[1]/a/text()',
                'title': 'div[1]/div[2]/h1/text()',
                'date': 'div[1]/div[2]/div/p[@class="date"]/text()',
                'tags': 'div[3]/div/a/text()',
                'contents': 'div[2]/div[1]/p/text()',
            }
    
            sel = response.xpath('//*[@id="main"]/div/div[1]/ul/li[2]/div/div')
    
            for col, path in need_path.items():
                crawler_contents = sel.xpath(path).extract()
                items[col] = [contents.strip() for contents in crawler_contents if len(contents.strip()) != 0]
    
            yield items
    

     

    그럼 아래와같이 스크래핑이 완료된당 😼 

    굿 아주좋당.

    {'category': ['✎ 21.上/Data'],
     'contents': ['크게',
                  '말 그대로 유사 사용자가 사용한 아이템을 추천해주거나 내가 소비한 아이템과 유사한 아이템을 추천해준다.',
                  'Latent Factor Collaborative Filtering:',
                  '알고리즘의 자세한 설명은 아래 블로거분이 상세하게 적어주셨다.',
                  '그러나 협업 필터링은 문제점이 존재한다.',
                  '(1) 콜드 스타트: 충분히 데이터가 수집되지못한 유저의 경우 추천이 어렵다.',
                  '(2) 롱테일: 파레토 법칙을 도식화했을 때 아래와 같이 나타난다. 이는 사용자들이 관심을 갖는 일부 콘텐츠가 '
                  '추천콘텐츠로 도출될 확률이 커진다.',
                  'TFIDF, Word2Vec,',
                  '요 근래, 내가 계속 진행했던 TFIDF와 같은 알고리즘이 컨텐츠 기반 필터링이다.',
                  '하기때문에 사용자의 정보가 필요하지 않아 콜드스타트의 문제가 없다.',
                  '컨텐츠 기반의 필터링의 단점은',
                  '위와 같이 알고리즘은 저마다의 단점을 가지고 있다. 때문에 각 알고리즘의 장점만을 수용하여 설계한 알고리즘들이 '
                  '오늘날 대부분 사용되고있으며, 현재 쏟아지고 있는 수많은 논문들 또한 이들의 단점을 보안할 방법들을 지속해서 '
                  '발표하고있다.',
                  '추가로 자료를 조사하면서,',
                  '이라는 현상을 알게되었다. 이러한 추천시스템으로 인해 사용자가 필터링 된 결과만을 접하게 되는 현상을 '
                  '이야기한다고한다.',
                  '하.. 추천시스템 너무 오룝다... 😢 머리 폭발할거같다.'],
     'date': ['2021. 4. 20. 16:05'],
     'tags': ['추천시스템', '필터버블', '협업필터링'],
     'title': ['추천 시스템의 기본, 협업필터링(collaborative filtering)']}
    
    {'category': ['✎ 21.上/개발공부'],
     'contents': ['논문 원본',
                  '위 논문은 Multi-armed bandit problem with context information를 줄여 '
                  'Contextual-Badit이라고 칭하였다. 그럼 시작에 앞서 multl-armed bandit인지 '
                  '알아봐야한다.',
                  '아래와 같이 여러 대의 슬롯머신이 있을 때, 어떤 슬롯머신의 레버를 당겨야할까? 라는 알고리즘이다.',
                  '이에 대한 전략은 당연하겠지만, 굉장히 많은 방법이 있다.',
                  '다시 정리하자면, 어떤 상황에 어떤 전략이 가장 우수할까.. 라는 알고리즘이다. 추천에선 기본일 것 같다.\xa0'
                  '가장 알맞는 아이템을 적시에 고객에게 추천을 해야하니까.',
                  '컨텍스츄얼(Contextual)은 상황에 맞춘이라는 의미로 이해할 수 있다. 개인화를 적용해서 선택을 한다.라고 '
                  '이해하면될 것 같다.',
                  '본 논문의',
                  '이다.'],
     'date': ['2021. 4. 17. 17:16'],
     'tags': ['밴딧', '알고리즘', '추천시스템', '컨테스츄얼밴딧'],
     'title': ['[논문 정리] A Contextual-Bandit Approach to Personalized News Article '
               'Recommendation']}
    
    {'category': ['✎ 21.上/Data'],
     'contents': ['아래 페이지에 따라 차근차근 따라하면 큰 무리없이 쥬피터 노트북까지 접근이 가능하다.',
                  '중간에 쥬피터 초기화가 조금 오래걸렸다.',
                  '쥬피터 노트북에 있는대로 그대로 실행만 해주면 다음과같이 원본데이터에서 rating>3 필터를 걸고 '
                  'rating필드를 없엔 데이터를 S3에 저장한다.',
                  '여기가 가장 중요한 것 같다. 노트북에도 아래와같이 작성되어있다.\xa0 아 이런식으로 해서 column이 달라도 '
                  '이해할 수 있도록 하는구나.',
                  'DataGroup은 사용전에 Active해줘야한다!',
                  '버켓에 접근하기 위해 policy 및 Roles 설정',
                  '아래 보면 트레이닝된 모델을 솔루션이라고 부른다고 한다. 또한 recipie는 아직 알고리즘에 트레이닝되지 않은 '
                  '데이터이다.',
                  'recepies 를 보면 아래와 같이 많은 정보들을 담고있다. 나도 나중에 데이터를 전달할때 아래와같이 자세한 '
                  '정보를 포함해야겠다고 생각했다.',
                  '솔루션을 생성하고 버전까지 생성한다음 active될때까지 기다리면 된다.',
                  '캠페인은 호스팅된 모델 버전이다. 마찬가지로 active될 때까지 기다린다.',
                  '이 다음에 Recommendation 받으면 된다.'],
     'date': ['2021. 4. 9. 17:55'],
     'tags': ['Amazon', 'AWS', 'Personalize', 'SageMaker', '추천'],
     'title': ['Amazon Personalize는 어떻게 동작하는걸까?']}
    

     

    3. Pipeline 

    데이터를 저장할 방식을 작성한 곳이다. 

    나는 일단 디비 연결은 나중으로 생각하고(s3에 저장하는게 최종목표) 일단은 간단하게 파일로 저장하게 했다. 

    from scrapy.exporters import CsvItemExporter, JsonItemExporter
    
    
    class Pipeline:
        """Super Class"""
        file_name = "TistoryCrawlerData"
        exporter = None
    
        def __init__(self, extension):
            self.file = open("%s.%s" % (self.file_name, extension), 'wb')
            self.exporter = self.exporter
    
        def process_item(self, item, spider):
            self.exporter.export_item(item)
            return item
    
        def spider_closed(self, spider):
            self.exporter.finish_exporting()
            self.file.close()
    
    
    class CsvPipeline(Pipeline):
        """Sub Class"""
    
        def __init__(self):
            extension = "csv"
            super().__init__(extension)
            self.exporter = CsvItemExporter(self.file, encoding='utf-8')
            self.exporter.start_exporting()
    
    
    class JsonPipeline(Pipeline):
        """Sub Class"""
    
        def __init__(self):
            extension="json"
            super().__init__(extension)
            self.exporter = JsonItemExporter(self.file, encoding='utf-8', indent=2)
            self.exporter.start_exporting()
    

     

    그리고나서 Setting.py 부분에 파이프라인을 선택해주면된다.

    ITEM_PIPELINES = {
        'tistory_.pipelines.JsonPipeline': 300,
        또는 ''tistory_.pipelines.CsvPipeline': 300,
    }

     

    그럼 선택한 파이프라인에 따라 아래처럼 파일이 생성되고 안에 내용도 완벽하당 ^>^!

     

     

    데이터프레임으로도 조작하기 쉬워졌다 얏홍~

     

    4. S3에 저장하기

    집에 가려다가,, 너모 아쉬워서 s3에 저장만하도록했다.

    class S3PipeLine:
        def __init__(self):
            self.REGION = "ap-northeast-2"
            self.AWSAccessKeyId = ">_<"
            self.AWSSecretKey = "secret"
            self.BUCKET_NAME = "tistorybucket"
    
        def process_item(self, item, spider):
            s3 = boto3.client(
                's3',
                region_name=self.REGION,
                aws_access_key_id=self.AWSAccessKeyId,
                aws_secret_access_key=self.AWSSecretKey
            )
    
            s3 = boto3.resource('s3')
            for bucket in s3.buckets.all():
                print(bucket)
    
            s3.Bucket(self.BUCKET_NAME).put_object(Key='TistroyCrawlerData.json', Body=pickle.dumps(item),
                                                   ACL='public-read')
    
            return item
    

     

    엑세스키랑 시크릿키는 아래 보안자격증명에서 만들 수 있다.

     

    처음에 코드에만 설정했더니 접근 거부가 떴다.

    <Error>
    <Code>AccessDenied</Code>
    <Message>Access Denied</Message>
    </Error>

    aws configure를 이용해 아이디와 시크릿을 추가해준다.. 

    그럼 해결 ^0^

     

    코드를 실행하면~ 

    저장도 잘되고 안에 내용도 잘 저장되어있다! ㅎㅎ 근데 인코딩 utf-8 설정좀 추가해야겠다^^;;

    REFERENCES

    blog.naver.com/PostView.nhn?blogId=rjs5730&logNo=221280231854&categoryNo=14&parentCategoryNo=0&viewDate=&currentPage=1&postListTopCurrentPage=1&from=search

    velog.io/@zoeyul/webcrawling