웹사이트 검색

Scrapy 및 Python 3으로 웹 페이지를 크롤링하는 방법


소개

웹 크롤링 또는 웹 스파이더링이라고도 하는 웹 스크래핑은 프로그래밍 방식으로 웹 페이지 모음을 살펴보고 데이터를 추출하는 행위이며 웹에서 데이터 작업을 위한 강력한 도구입니다.

웹 스크레이퍼를 사용하면 일련의 제품에 대한 데이터를 마이닝하거나, 가지고 놀 수 있는 대량의 텍스트 또는 정량적 데이터를 얻거나, 공식 API 없이 사이트에서 데이터를 검색하거나, 개인적인 호기심을 만족시킬 수 있습니다.

이 자습서에서는 재미있는 데이터 세트를 탐색하면서 스크래핑 및 스파이더링 프로세스의 기본 사항에 대해 배웁니다. 우리는 웹 스파이더를 테스트하기 위해 설계된 사이트에서 호스팅되는 견적 데이터베이스인 Quotes to Scrape를 사용할 것입니다. 이 튜토리얼이 끝나면 따옴표가 포함된 일련의 페이지를 살펴보고 화면에 표시하는 완전한 기능의 Python 웹 스크레이퍼를 갖게 됩니다.

스크레이퍼는 쉽게 확장할 수 있으므로 웹에서 데이터를 스크랩하는 자신의 프로젝트를 위한 기반으로 사용할 수 있습니다.

전제 조건

이 자습서를 완료하려면 Python 3용 로컬 개발 환경이 필요합니다. Python 3용 로컬 프로그래밍 환경을 설치 및 설정하는 방법에 따라 필요한 모든 것을 구성할 수 있습니다.

1단계 - 기본 스크레이퍼 만들기

스크래핑은 두 단계 프로세스입니다.

  1. 웹 페이지를 체계적으로 찾고 다운로드합니다.
  2. 다운로드한 페이지에서 정보를 추출합니다.

이러한 두 단계는 여러 언어로 다양한 방법으로 구현될 수 있습니다.

프로그래밍 언어에서 제공하는 모듈이나 라이브러리를 사용하여 처음부터 스크레이퍼를 구축할 수 있지만 스크레이퍼가 더 복잡해짐에 따라 몇 가지 잠재적인 골칫거리를 처리해야 합니다. 예를 들어 한 번에 둘 이상의 페이지를 크롤링할 수 있도록 동시성을 처리해야 합니다. 스크랩한 데이터를 CSV, XML 또는 JSON과 같은 다양한 형식으로 변환하는 방법을 알고 싶을 것입니다. 그리고 때때로 특정 설정과 액세스 패턴이 필요한 사이트를 처리해야 합니다.

이러한 문제를 처리하는 기존 라이브러리 위에 스크레이퍼를 빌드하면 더 나은 행운을 얻을 수 있습니다. 이 튜토리얼에서는 Python과 Scrapy를 사용하여 스크레이퍼를 빌드할 것입니다.

Scrapy는 가장 인기 있고 강력한 Python 스크래핑 라이브러리 중 하나입니다. 스크래핑에 "배터리 포함\ 접근 방식을 취합니다. 즉, 모든 스크레이퍼에 필요한 많은 공통 기능을 처리하므로 개발자가 매번 휠을 재발명할 필요가 없습니다.

대부분의 Python 패키지와 마찬가지로 Scrapy는 PyPI(pip라고도 함)에 있습니다. Python Package Index인 PyPI는 게시된 모든 Python 소프트웨어의 커뮤니티 소유 리포지토리입니다.

이 자습서의 전제 조건에 설명된 것과 같은 Python 설치가 있는 경우 컴퓨터에 이미 pip가 설치되어 있으므로 다음 명령을 사용하여 Scrapy를 설치할 수 있습니다.

  1. pip install scrapy

설치에 문제가 있거나 pip를 사용하지 않고 Scrapy를 설치하려는 경우 공식 설치 문서를 확인하세요.

Scrapy가 설치된 상태에서 프로젝트를 위한 새 폴더를 만듭니다. 다음을 실행하여 터미널에서 이 작업을 수행할 수 있습니다.

  1. mkdir quote-scraper

이제 방금 만든 새 디렉터리로 이동합니다.

  1. cd quote-scraper

그런 다음 scraper.py라는 스크레이퍼용 새 Python 파일을 만듭니다. 이 튜토리얼을 위해 이 파일에 모든 코드를 배치할 것입니다. 선택한 편집 소프트웨어를 사용하여 이 파일을 만들 수 있습니다.

Scrapy를 기초로 사용하는 매우 기본적인 스크레이퍼를 만들어 프로젝트를 시작합니다. 그렇게 하려면 Scrapy에서 제공하는 기본 스파이더 클래스인 scrapy.Spider를 하위 클래스로 만드는 Python 클래스를 만들어야 합니다. 이 클래스에는 두 가지 필수 속성이 있습니다.

  • name — 거미의 이름입니다.
  • start_urls — 크롤링을 시작하는 URL 목록입니다. 하나의 URL로 시작하겠습니다.

텍스트 편집기에서 scrapy.py 파일을 열고 다음 코드를 추가하여 기본 스파이더를 만듭니다.

import scrapy


class QuoteSpider(scrapy.Spider):
    name = 'quote-spdier'
    start_urls = ['https://quotes.toscrape.com']

이 내용을 한 줄씩 분해해 보겠습니다.

먼저 패키지가 제공하는 클래스를 사용할 수 있도록 scrapy를 가져옵니다.

다음으로 Scrapy에서 제공하는 Spider 클래스를 가져와 BrickSetSpider라는 하위 클래스를 만듭니다. 하위 클래스를 상위 클래스의 보다 특수화된 형태로 생각하십시오. Spider 클래스에는 URL을 따라가는 방법과 찾은 페이지에서 데이터를 추출하는 방법을 정의하는 메서드와 동작이 있지만 어디를 찾아야 하는지, 어떤 데이터를 찾아야 하는지는 모릅니다. 서브클래싱을 통해 해당 정보를 제공할 수 있습니다.

마지막으로 클래스 이름을 quote-spider로 지정하고 스크레이퍼에 https://quotes.toscrape.com에서 시작하는 단일 URL을 제공합니다. 브라우저에서 해당 URL을 열면 많은 유명한 인용문 페이지 중 첫 번째 페이지를 보여주는 검색 결과 페이지로 이동합니다.

이제 스크레이퍼를 테스트합니다. 일반적으로 Python 파일은 python path/to/file.py와 같은 명령으로 실행됩니다. 그러나 Scrapy에는 자체 명령줄 인터페이스가 있어 스크레이퍼 시작 프로세스를 간소화합니다. 다음 명령으로 스크레이퍼를 시작합니다.

  1. scrapy runspider scraper.py

명령은 다음과 같이 출력됩니다.

Output
2022-12-02 10:30:08 [scrapy.utils.log] DEBUG: Using reactor: twisted.internet.epollreactor.EPollReactor 2022-12-02 10:30:08 [scrapy.extensions.telnet] INFO: Telnet Password: b4d94e3a8d22ede1 2022-12-02 10:30:08 [scrapy.middleware] INFO: Enabled extensions: ['scrapy.extensions.corestats.CoreStats', ... 'scrapy.extensions.logstats.LogStats'] 2022-12-02 10:30:08 [scrapy.middleware] INFO: Enabled downloader middlewares: ['scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware', ... 'scrapy.downloadermiddlewares.stats.DownloaderStats'] 2022-12-02 10:30:08 [scrapy.middleware] INFO: Enabled spider middlewares: ['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware', ... 'scrapy.spidermiddlewares.depth.DepthMiddleware'] 2022-12-02 10:30:08 [scrapy.middleware] INFO: Enabled item pipelines: [] 2022-12-02 10:30:08 [scrapy.core.engine] INFO: Spider opened 2022-12-02 10:30:08 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min) 2022-12-02 10:30:08 [scrapy.extensions.telnet] INFO: Telnet console listening on 127.0.0.1:6023 2022-12-02 10:49:32 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://quotes.toscrape.com> (referer: None) 2022-12-02 10:30:08 [scrapy.core.engine] INFO: Closing spider (finished) 2022-12-02 10:30:08 [scrapy.statscollectors] INFO: Dumping Scrapy stats: {'downloader/request_bytes': 226, ... 'start_time': datetime.datetime(2022, 12, 2, 18, 30, 8, 492403)} 2022-12-02 10:30:08 [scrapy.core.engine] INFO: Spider closed (finished)

출력이 많으니 분해해 보겠습니다.

  • 스크레이퍼는 URL에서 데이터 읽기를 처리하는 데 필요한 추가 구성 요소와 확장 프로그램을 초기화하고 로드했습니다.
  • start_urls 목록에서 제공한 URL을 사용하고 웹 브라우저에서와 마찬가지로 HTML을 가져왔습니다.
  • 그 HTML을 기본적으로 아무 것도 하지 않는 parse 메서드로 전달했습니다. 우리는 우리 자신의 parse 메서드를 작성하지 않았기 때문에 스파이더는 아무 작업도 하지 않고 그냥 끝냅니다.

이제 페이지에서 일부 데이터를 가져오겠습니다.

2단계 - 페이지에서 데이터 추출

우리는 페이지를 풀다운하는 매우 기본적인 프로그램을 만들었지만 아직 스크래핑이나 스파이더링은 하지 않습니다. 추출할 데이터를 제공하겠습니다.

스크랩하려는 페이지를 보면 다음과 같은 구조를 가지고 있음을 알 수 있습니다.

  • 모든 페이지에 헤더가 있습니다.
  • 로그인 링크가 있습니다.
  • 그런 다음 테이블이나 정렬된 목록처럼 보이는 인용 부호 자체가 있습니다. 각 인용문의 형식은 비슷합니다.

스크레이퍼를 작성할 때 HTML 파일의 소스를 살펴보고 구조에 익숙해져야 합니다. 가독성을 위해 목표와 관련이 없는 태그를 제거한 상태입니다.

quotes.toscrape.com
<body> ... <div class="quote" itemscope itemtype="http://schema.org/CreativeWork"> <span class="text" itemprop="text">“I have not failed. I&#39;ve just found 10,000 ways that won&#39;t work.”</span> <span>by <small class="author" itemprop="author">Thomas A. Edison <a href="/author/Thomas-A-Edison">(about)</a> </span> <div class="tags"> Tags: <meta class="keywords" itemprop="keywords" content="edison,failure,inspirational,paraphrased" / > <a class="tag" href="/tag/edison/page/1/">edison</a> <a class="tag" href="/tag/failure/page/1/">failure</a> <a class="tag" href="/tag/inspirational/page/1/">inspirational</a> <a class="tag" href="/tag/paraphrased/page/1/">paraphrased</a> </div> </div> ... </body>

이 페이지를 스크랩하는 것은 2단계 프로세스입니다.

  1. 먼저 원하는 데이터가 있는 페이지 부분을 찾아 각 인용문을 가져옵니다.
  2. 그런 다음 각 인용문에 대해 HTML 태그에서 데이터를 가져와 원하는 데이터를 가져옵니다.

scrapy는 사용자가 제공하는 선택기를 기반으로 데이터를 가져옵니다. 선택기는 페이지에서 하나 이상의 요소를 찾는 데 사용할 수 있는 패턴이므로 요소 내의 데이터로 작업할 수 있습니다. scrapyCSS 선택기 또는 XPath 선택기를 지원합니다.

CSS는 페이지의 모든 집합을 찾는 데 완벽하게 적합하므로 지금은 CSS 선택기를 사용할 것입니다. HTML을 보면 각 따옴표가 클래스 따옴표로 지정되어 있음을 알 수 있습니다. 클래스를 찾고 있으므로 CSS 선택기에 .quote를 사용합니다. 선택기의 . 부분은 요소에서 class 특성을 검색합니다. 우리가 해야 할 일은 다음과 같이 클래스에서 parse라는 새 메서드를 만들고 해당 선택기를 response 객체로 전달하는 것입니다.

class QuoteSpider(scrapy.Spider):
    name = 'quote-spdier'
    start_urls = ['https://quotes.toscrape.com']

    def parse(self, response):
        QUOTE_SELECTOR = '.quote'
        TEXT_SELECTOR = '.text::text'
        AUTHOR_SELECTOR = '.author::text'
        
        for quote in response.css(QUOTE_SELECTOR):
            pass

이 코드는 페이지의 모든 집합을 잡고 반복하여 데이터를 추출합니다. 이제 해당 인용문에서 데이터를 추출하여 표시할 수 있습니다.

우리가 파싱하고 있는 페이지의 소스를 다시 보면 각 인용문의 텍스트가 text 클래스와 인용문 작성자가 포함된 span 내에 저장되어 있음을 알 수 있습니다. author 클래스가 있는 <small> 태그:

quotes.toscrape.com
... <span class="text" itemprop="text">“I have not failed. I&#39;ve just found 10,000 ways that won&#39;t work.”</span> <span>by <small class="author" itemprop="author">Thomas A. Edison ...

우리가 반복하고 있는 quote 개체에는 자체 css 메서드가 있으므로 선택기를 전달하여 자식 요소를 찾을 수 있습니다. 다음과 같이 코드를 수정하여 집합의 이름을 찾아 표시합니다.

class QuoteSpider(scrapy.Spider):
    name = 'quote-spdier'
    start_urls = ['https://quotes.toscrape.com']

    def parse(self, response):
        QUOTE_SELECTOR = '.quote'
        TEXT_SELECTOR = '.text::text'
        AUTHOR_SELECTOR = '.author::text'
        
        for quote in response.css(QUOTE_SELECTOR):
            yield {
                'text': quote.css(TEXT_SELECTOR).extract_first(),
                'author': quote.css(AUTHOR_SELECTOR).extract_first(),
            }

참고: extract_first() 뒤에 오는 쉼표는 오타가 아닙니다. Python에서 dict 개체의 후행 쉼표는 유효한 구문이며 항목을 더 추가할 여지를 남겨두는 좋은 방법입니다. 나중에 설명하겠습니다.

이 코드에서 진행되는 두 가지 사항을 알 수 있습니다.

  • 인용문 및 저자 선택기에 ::text를 추가합니다. 태그 자체가 아니라 태그 내부의 텍스트를 가져오는 CSS 의사 선택기입니다.
  • quote.css(TEXT_SELECTOR)에 의해 반환된 개체에서 extract_first()를 호출합니다. 선택자와 일치하는 첫 번째 요소를 원하기 때문입니다. 이는 요소 목록이 아닌 문자열을 제공합니다.

파일을 저장하고 스크레이퍼를 다시 실행하십시오.

  1. scrapy runspider scraper.py

이번에는 출력에 인용문과 작성자가 포함됩니다.

Output
... 2022-12-02 11:00:53 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com> {'text': '“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”', 'author': 'Albert Einstein'} 2022-12-02 11:00:53 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com> {'text': '“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”', 'author': 'Jane Austen'} 2022-12-02 11:00:53 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com> {'text': "“Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.”", 'author': 'Marilyn Monroe'} ...

저자에 대한 페이지 링크에 대한 새로운 선택기와 인용구에 대한 태그를 추가하여 이를 계속 확장해 보겠습니다. 각 인용문에 대한 HTML을 조사하여 다음을 발견했습니다.

  • 작성자의 정보 페이지 링크는 이름 바로 뒤에 있는 링크에 저장됩니다.
  • 태그는 각각 태그로 분류된 a 태그의 모음으로 저장되며 tags와 함께 div 요소 내에 저장됩니다. 클래스.

따라서 이 새로운 정보를 얻기 위해 스크레이퍼를 수정해 보겠습니다.

class QuoteSpider(scrapy.Spider):
    name = 'quote-spdier'
    start_urls = ['https://quotes.toscrape.com']

    def parse(self, response):
        QUOTE_SELECTOR = '.quote'
        TEXT_SELECTOR = '.text::text'
        AUTHOR_SELECTOR = '.author::text'
        ABOUT_SELECTOR = '.author + a::attr("href")'
        TAGS_SELECTOR = '.tags > .tag::text'

        for quote in response.css(QUOTE_SELECTOR):
            yield {
                'text': quote.css(TEXT_SELECTOR).extract_first(),
                'author': quote.css(AUTHOR_SELECTOR).extract_first(),
                'about': 'https://quotes.toscrape.com' + 
                        quote.css(ABOUT_SELECTOR).extract_first(),
                'tags': quote.css(TAGS_SELECTOR).extract(),
            }

변경 사항을 저장하고 스크레이퍼를 다시 실행하십시오.

  1. scrapy runspider scraper.py

이제 출력에 새 데이터가 포함됩니다.

Output
2022-12-02 11:14:28 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com> {'text': '“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”', 'author': 'Albert Einstein', 'about': 'https://quotes.toscrape.com/author/Albert-Einstein', 'tags': ['inspirational', 'life', 'live', 'miracle', 'miracles']} 2022-12-02 11:14:28 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com> {'text': '“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”', 'author': 'Jane Austen', 'about': 'https://quotes.toscrape.com/author/Jane-Austen', 'tags': ['aliteracy', 'books', 'classic', 'humor']} 2022-12-02 11:14:28 [scrapy.core.scraper] DEBUG: Scraped from <200 https://quotes.toscrape.com> {'text': "“Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.”", 'author': 'Marilyn Monroe', 'about': 'https://quotes.toscrape.com/author/Marilyn-Monroe', 'tags': ['be-yourself', 'inspirational']}

이제 이 스크레이퍼를 링크를 따라가는 거미로 바꾸겠습니다.

3단계 - 여러 페이지 크롤링

해당 초기 페이지에서 데이터를 성공적으로 추출했지만 나머지 결과를 보기 위해 계속 진행하지는 않습니다. 스파이더의 요점은 다른 페이지에 대한 링크를 감지 및 통과하고 해당 페이지에서도 데이터를 가져오는 것입니다.

각 페이지의 상단과 하단에 결과의 다음 페이지로 연결되는 작은 오른쪽 캐럿(>)이 있음을 알 수 있습니다. 이에 대한 HTML은 다음과 같습니다.

quotes.toscrape.com
... <nav> <ul class="pager"> <li class="next"> <a href="/page/2/">Next <span aria-hidden="true">&rarr;</span></a> </li> </ul> </nav> ...

소스에서 next 클래스가 있는 li 태그를 찾을 수 있으며 해당 태그 안에는 다음 링크가 있는 a 태그가 있습니다. 다음 페이지. 우리가 해야 할 일은 스크레이퍼에게 해당 링크가 존재하는 경우 이를 따르도록 지시하는 것입니다.

다음과 같이 코드를 수정하십시오.

class QuoteSpider(scrapy.Spider):
    name = 'quote-spdier'
    start_urls = ['https://quotes.toscrape.com']

    def parse(self, response):
        QUOTE_SELECTOR = '.quote'
        TEXT_SELECTOR = '.text::text'
        AUTHOR_SELECTOR = '.author::text'
        ABOUT_SELECTOR = '.author + a::attr("href")'
        TAGS_SELECTOR = '.tags > .tag::text'
        NEXT_SELECTOR = '.next a::attr("href")'

        for quote in response.css(QUOTE_SELECTOR):
            yield {
                'text': quote.css(TEXT_SELECTOR).extract_first(),
                'author': quote.css(AUTHOR_SELECTOR).extract_first(),
                'about': 'https://quotes.toscrape.com' + 
                        quote.css(ABOUT_SELECTOR).extract_first(),
                'tags': quote.css(TAGS_SELECTOR).extract(),
            }

        next_page = response.css(NEXT_SELECTOR).extract_first()
        if next_page:
            yield scrapy.Request(response.urljoin(next_page))

먼저, "다음 페이지\ 링크에 대한 선택기를 정의하고, 첫 번째 일치 항목을 추출하고, 존재하는지 확인합니다. scrapy.Request는 Scrapy가 알고 있는 새로운 요청 개체입니다. 다음에 구문 분석합니다.

즉, 다음 페이지로 이동하면 거기에서 다음 페이지에 대한 링크를 찾고 해당 페이지에서 다음 페이지에 대한 링크를 찾는 식으로 찾을 수 없을 때까지 계속됩니다. 다음 페이지에 대한 링크. 이것은 웹 스크래핑의 핵심 부분입니다. 링크를 찾고 따라가는 것입니다. 이 예에서는 매우 선형적입니다. 한 페이지에는 마지막 페이지에 도달할 때까지 다음 페이지에 대한 링크가 있지만 태그, 다른 검색 결과 또는 원하는 다른 URL에 대한 링크를 따라갈 수 있습니다.

이제 코드를 저장하고 스파이더를 다시 실행하면 세트의 첫 번째 페이지를 반복하면 스파이더가 멈추지 않는다는 것을 알 수 있습니다. 10페이지 전체에 걸쳐 100개의 인용문을 계속해서 살펴봅니다. 대대적으로 큰 데이터 덩어리는 아니지만 이제 스크랩할 새 페이지를 자동으로 찾는 프로세스를 알고 있습니다.

이 튜토리얼을 위해 완성된 코드는 다음과 같습니다.

import scrapy


class QuoteSpider(scrapy.Spider):
    name = 'quote-spdier'
    start_urls = ['https://quotes.toscrape.com']

    def parse(self, response):
        QUOTE_SELECTOR = '.quote'
        TEXT_SELECTOR = '.text::text'
        AUTHOR_SELECTOR = '.author::text'
        ABOUT_SELECTOR = '.author + a::attr("href")'
        TAGS_SELECTOR = '.tags > .tag::text'
        NEXT_SELECTOR = '.next a::attr("href")'

        for quote in response.css(QUOTE_SELECTOR):
            yield {
                'text': quote.css(TEXT_SELECTOR).extract_first(),
                'author': quote.css(AUTHOR_SELECTOR).extract_first(),
                'about': 'https://quotes.toscrape.com' + 
                        quote.css(ABOUT_SELECTOR).extract_first(),
                'tags': quote.css(TAGS_SELECTOR).extract(),
            }

        next_page = response.css(NEXT_SELECTOR).extract_first()
        if next_page:
            yield scrapy.Request(
                response.urljoin(next_page),
            )

결론

이 자습서에서는 30줄 미만의 코드로 웹 페이지에서 데이터를 추출하는 완전한 기능을 갖춘 스파이더를 구축했습니다. 좋은 시작이지만 이 거미로 할 수 있는 재미있는 일이 많이 있습니다. 그것은 당신이 생각하고 실험하게하기에 충분할 것입니다. Scrapy에 대한 자세한 정보가 필요하면 "How To Scrape Web Pages with Beautiful Soup and Python 3\을 확인하세요.