Scrapy 크롤러 프레임 워크를 사용하여 음식 포럼 데이터를 크롤링하고 데이터베이스에 저장하는 방법을 알려줍니다.

    안녕하세요 여러분, 이런 나눔 프로젝트에 대한 글은 이번이 처음입니다. 매우 물이 많고 불완전 할 수도 있고, 실수가있을 것 같습니다. 댓글에 조언을 주시면 감사하겠습니다. 감사합니다!

I. 소개

웹 크롤러 (웹 스파이더 또는 웹 로봇이라고도 함)는 특정 규칙에 따라 World Wide Web에서 정보를 자동으로 크롤링하는 프로그램 또는 스크립트입니다. 덜 일반적으로 사용되는 다른 이름은 개미, 자동 인덱싱, 시뮬레이터 또는 웜입니다. ------ 바이두 백과 사전

    인간의 관점에서 크롤러는 정기적으로 방대한 양의 데이터를 획득 한 다음 처리하고 사용하는 데 사용되며 빅 데이터, 금융, 머신 러닝 등에서 필요한 지원 조건 중 하나입니다.

    현재 1 계층 도시에서는 크롤러의 급여와 대우가 상대적으로 객관적이며 나중에 중급 및 상급 크롤러 엔지니어, 데이터 분석가 및 빅 데이터 개발 직위로의 승진은 모두 좋은 전환입니다.

 

2. 프로젝트 목표

    여기에 소개 된 프로젝트는 너무 복잡 할 필요가 없습니다. 궁극적 인 목표는 게시물의 각 댓글을 데이터베이스로 크롤링하고 데이터를 업데이트하고 반복 크롤링, 크롤링 방지 및 기타 조치를 방지하는 것입니다.

 

3. 프로젝트 준비

이 부분에서는 주로이 기사에서 사용 된 도구, 관련 라이브러리, 웹 페이지 및 기타 정보 등을 소개합니다.

소프트웨어 : PyCharm

필수 라이브러리 : Scrapy, selenium, pymongo, user_agent, datetime

타겟 웹 사이트 :

http://bbs.foodmate.net

플러그인 : chromedriver (버전이 정확해야 함)

 

네, 프로젝트 분석

1. 크롤링 웹 사이트의 구조 결정

    요컨대 : 웹 사이트의 로딩 방법, 게시물을 올바르게 입력하여 레벨별로 데이터 레벨을 확보하는 방법, 데이터를 저장하는 데 사용할 형식 등을 결정하십시오.

    둘째, 웹 사이트의 계층 적 구조, 즉 섹션에 따라 조금씩 게시물 페이지에 들어가는 방법을 관찰하십시오. 이것은 크롤러 작업에 매우 중요하며 코드 작성의 주요 부분이기도합니다.

 

2. 데이터를 크롤링하는 올바른 방법을 선택하는 방법은 무엇입니까?

    현재 내가 아는 크롤러 방법은 다음과 같습니다 (불완전하지만 더 일반적으로 사용됨).

    1) 요청 프레임 워크 :이 http 라이브러리는 필요한 데이터를 유연하고 간단하게 크롤링하는 데 사용할 수 있지만 프로세스가 약간 번거롭고 패킷 캡처 도구와 함께 사용하여 데이터를 얻을 수 있습니다. 그러나 헤더와 해당 요청 매개 변수를 결정해야합니다. 그렇지 않으면 데이터를 얻을 수 없습니다. 많은 앱 크롤링, 이미지 및 비디오 크롤링, 크롤링 및 중지, 비교적 가볍고 유연하며 높은 동시성 및 분산 배포도 매우 유연합니다. , 기능은 더 좋은 실현이 될 수 있습니다.

    2) 스크래피 프레임 워크 : 스크래피 프레임 워크는 크롤러에게 가장 일반적으로 사용되는 최고의 크롤러 프레임 워크라고 할 수 있습니다. 스크래피는 비동기식이며 일반 대신 더 읽기 쉬운 xpath 사용, 강력한 통계 및 로그 시스템, 동시에 다른 URL에서 크롤링, 독립적 인 디버깅을 용이하게하는 셸 모드 지원, 일부 통합 필터 작성을 용이하게하는 미들웨어 작성 지원, 파이프 라인을 통해 데이터베이스에 저장할 수 있습니다. 이것은 또한이 기사에서 소개 할 프레임 워크입니다 (셀레늄 라이브러리와 결합).

 

다섯, 프로젝트 실현

1. 첫 번째 단계 : 웹 사이트 유형 결정

    먼저 그것이 무엇을 의미하고 어떤 웹 사이트를 볼 것인지 설명하십시오. 먼저 웹 사이트의 로딩 방법이 정적 로딩, 동적 로딩 (js 로딩) 또는 다른 방법인지 확인해야합니다. 그것을 다루는 방법. 그런 다음 오늘 크롤링 된 웹 사이트를 관찰 한 결과이 포럼이 연대순이라는 사실을 발견했습니다. 먼저 정적으로로드 된 웹 사이트라고 추측하고 아래 그림과 같이 js로드를 구성하기 위해 플러그인을 열었습니다.

영상

    새로 고침 후 실제로 정적 웹 사이트임을 확인합니다 (정상적으로로드 할 수 있다면 기본적으로 정적으로로드됩니다).

 

2. 2 단계 : 계층 결정

    둘째, 오늘 크롤링하려는 웹 사이트는 정적으로로드 된 웹 사이트 인 음식 포럼 웹 사이트입니다. 이전 분석에서 이미 이해 한 다음 계층 구조를 확인했습니다.

 

영상

    아마도 위의 프로세스에는 총 세 가지 수준의 점진적 액세스가 있으며 아래 그림과 같이 게시물 페이지에 도달합니다.

영상

많은 사람들이 파이썬을 배우고 어디서부터 시작해야할지 모릅니다.
많은 사람들이 파이썬을 배우고 기본 문법을 습득 한 후 시작할 사례를 어디에서 찾을 지 모릅니다.
사례 연구를 수행 한 많은 사람들은 고급 지식을 배우는 방법을 모릅니다.
따라서이 세 가지 유형의 사람들을 위해 비디오 자습서, 전자 책 및 과정의 소스 코드를 무료로받을 수있는 좋은 학습 플랫폼을 제공 할 것입니다!
QQ 그룹 : 721195303

코드 표시의 일부 :

    1 단계 인터페이스 :

def parse(self, response):
    self.logger.info("已进入网页!")
    self.logger.info("正在获取版块列表!")
    column_path_list = response.css('#ct > div.mn > div:nth-child(2) > div')[:-1]
    for column_path in column_path_list:
        col_paths = column_path.css('div > table > tbody > tr > td > div > a').xpath('@href').extract()
        for path in col_paths:
            block_url = response.urljoin(path)
            yield scrapy.Request(
                url=block_url,
                callback=self.get_next_path,
            )

    보조 인터페이스 :

def get_next_path(self, response):
    self.logger.info("已进入版块!")
    self.logger.info("正在获取文章列表!")
    if response.url == 'http://www.foodmate.net/know/':
        pass
    else:
        try:
            nums = response.css('#fd_page_bottom > div > label > span::text').extract_first().split(' ')[-2]
        except:
            nums = 1
        for num in range(1, int(nums) + 1):
            tbody_list = response.css('#threadlisttableid > tbody')
            for tbody in tbody_list:
                if 'normalthread' in str(tbody):
                    item = LunTanItem()
                    item['article_url'] = response.urljoin(
                        tbody.css('* > tr > th > a.s.xst').xpath('@href').extract_first())
                    item['type'] = response.css(
                        '#ct > div > div.bm.bml.pbn > div.bm_h.cl > h1 > a::text').extract_first()
                    item['title'] = tbody.css('* > tr > th > a.s.xst::text').extract_first()
                    item['spider_type'] = "论坛"
                    item['source'] = "食品论坛"
                    if item['article_url'] != 'http://bbs.foodmate.net/':
                        yield scrapy.Request(
                            url=item['article_url'],
                            callback=self.get_data,
                            meta={'item': item, 'content_info': []}
                        )
        try:
            callback_url = response.css('#fd_page_bottom > div > a.nxt').xpath('@href').extract_first()
            callback_url = response.urljoin(callback_url)
            yield scrapy.Request(
                url=callback_url,
                callback=self.get_next_path,
            )
        except IndexError:
            pass

    3 단계 인터페이스 :

def get_data(self, response):
    self.logger.info("正在爬取论坛数据!")
    item = response.meta['item']
    content_list = []
    divs = response.xpath('//*[@id="postlist"]/div')
    user_name = response.css('div > div.pi > div:nth-child(1) > a::text').extract()
    publish_time = response.css('div.authi > em::text').extract()
    floor = divs.css('* strong> a> em::text').extract()
    s_id = divs.xpath('@id').extract()
    for i in range(len(divs) - 1):
        content = ''
        try:

            strong = response.css('#postmessage_' + s_id[i].split('_')[-1] + '').xpath('string(.)').extract()
            for s in strong:
                content += s.split(';')[-1].lstrip('\r\n')
            datas = dict(content=content,  # 内容
                         reply_id=0,  # 回复的楼层,默认0
                         user_name=user_name[i],  # ⽤户名
                         publish_time=publish_time[i].split('于 ')[-1],  # %Y-%m-%d %H:%M:%S'
                         id='#' + floor[i],  # 楼层
                         )
            content_list.append(datas)
        except IndexError:
            pass
    item['content_info'] = response.meta['content_info']
    item['scrawl_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    item['content_info'] += content_list

    data_url = response.css('#ct > div.pgbtn > a').xpath('@href').extract_first()
    if data_url != None:
        data_url = response.urljoin(data_url)
        yield scrapy.Request(
            url=data_url,
            callback=self.get_data,
            meta={'item': item, 'content_info': item['content_info']}
        )
    else:
        item['scrawl_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        self.logger.info("正在存储!")
        print('储存成功')
        yield item

 

3. 3 단계 : 크롤링 방법 결정

    정적 인 웹 페이지이기 때문에 처음에는 스크래피 프레임 워크를 사용하여 직접 데이터를 얻기로 결정했고, 예비 테스트를 통해 그 방법이 실제로 타당하다는 것을 알게되었지만 그 당시에는 어리고 경박하고 과소 평가되었습니다. 제한적인 인내로 인해 크롤링을 제한하는 타이머가 추가되지 않았습니다. 속도로 인해 웹 사이트에 제한을 받게되었고, 웹 사이트에 들어가기 전에 정적으로로드 된 웹 페이지에서 동적로드 웹 페이지 확인 알고리즘으로 변경되었습니다. 웹 페이지에서 직접 액세스는 백그라운드에서 거부됩니다.

    근데 이런 문제가 어떻게 내 영리 할 수 ​​있겠는가? 짧은 생각 (1 일) 끝에 스크래피 프레임 워크 + 셀레늄 라이브러리 방식으로 계획을 변경하고 크롬 드라이버를 호출하여 웹 사이트 방문을 시뮬레이션했습니다. 로드되면 크롤링되지 않습니다. 그게 다입니다. 후속 조치는이 방법이 실제로 실행 가능하고 효율적이라는 것을 증명합니다.

    코드의 구현 부분은 다음과 같습니다.

def process_request(self, request, spider):
    chrome_options = Options()
    chrome_options.add_argument('--headless')  # 使用无头谷歌浏览器模式
    chrome_options.add_argument('--disable-gpu')
    chrome_options.add_argument('--no-sandbox')
    # 指定谷歌浏览器路径
    self.driver = webdriver.Chrome(chrome_options=chrome_options,
                                   executable_path='E:/pycharm/workspace/爬虫/scrapy/chromedriver')
    if request.url != 'http://bbs.foodmate.net/':
        self.driver.get(request.url)
        html = self.driver.page_source
        time.sleep(1)
        self.driver.quit()
        return scrapy.http.HtmlResponse(url=request.url, body=html.encode('utf-8'), encoding='utf-8',
                                        request=request)

 

4. 4 단계 : 크롤링 된 데이터의 저장 형식 결정

    말할 필요도없이이 부분은 자신의 필요에 따라 items.py에서 크롤링해야하는 데이터 형식을 설정합니다. 이 형식을 사용하여 프로젝트에 저장하십시오.

class LunTanItem(scrapy.Item):
    """
        论坛字段
    """
    title = Field()  # str: 字符类型 | 论坛标题
    content_info = Field()  # str: list类型 | 类型list: [LunTanContentInfoItem1, LunTanContentInfoItem2]
    article_url = Field()  # str: url | 文章链接
    scrawl_time = Field()  # str: 时间格式 参照如下格式 2019-08-01 10:20:00 | 数据爬取时间
    source = Field()  # str: 字符类型 | 论坛名称 eg: 未名BBS, 水木社区, 天涯论坛
    type = Field()  # str: 字符类型 | 板块类型 eg: '财经', '体育', '社会'
    spider_type = Field()  # str: forum | 只能写 'forum'

 

5. 5 단계 : 데이터베이스 저장 확인

    이 프로젝트를 위해 선택한 데이터베이스는 mongodb입니다. 비 관계형 데이터베이스이기 때문에 장점이 분명하고 형식 요구 사항이 그리 높지 않으며 다차원 데이터를 유연하게 저장할 수 있습니다. 일반적으로 크롤러가 선호하는 데이터베이스입니다. (redis라고 말하지 마라, 내가 그것을 안다면 나는 그것을 사용할 것이다, 주로 그렇지 않다)

    암호:

import pymongo

class FMPipeline():
    def __init__(self):
        super(FMPipeline, self).__init__()
        # client = pymongo.MongoClient('139.217.92.75')
        client = pymongo.MongoClient('localhost')
        db = client.scrapy_FM
        self.collection = db.FM

    def process_item(self, item, spider):
        query = {
            'article_url': item['article_url']
        }
        self.collection.update_one(query, {"$set": dict(item)}, upsert=True)
        return item

    이때 일부 똑똑한 친구는 다음과 같이 질문합니다. 동일한 데이터가 두 번 크롤링되면 어떻게됩니까? (즉, 중복 체크 기능입니다)

    전에는이 질문에 대해 생각하지 않았습니다. 나중에 큰 사람들에게 물었을 때 알게되었습니다. 이것은 데이터를 저장할 때 수행되었습니다.이 문장 만 :

query = {
    'article_url': item['article_url']
}
self.collection.update_one(query, {"$set": dict(item)}, upsert=True)

    게시물의 링크를 통해 크롤링되는 중복 데이터가 있는지 확인하고, 중복 된 경우이를 덮는 것으로 이해할 수 있으므로 데이터도 업데이트 할 수 있습니다.

 

6. 기타 설정

    멀티 스레딩, 헤더, 파이프 라인 전송 순서 등과 같은 문제는 모두 settings.py 파일에 설정되어 있습니다. 자세한 내용은 편집자의 프로젝트를 참조하여 확인하시기 바랍니다. 여기서 자세한 내용은 다루지 않겠습니다.

 

일곱, 효과 표시

    1. 실행을 클릭하면 아래 그림과 같이 결과가 콘솔에 표시됩니다.

영상

영상

    2. 중간에는 많은 포스트를 큐에 쌓아 올리는 크롤링 작업과 멀티 스레드 처리가있을 예정인데 16 개의 스레드를 설정했는데 속도는 여전히 매우 인상적입니다.

영상

    3. 데이터베이스 데이터 표시 :

영상

    content_info는 각 게시물의 모든 댓글과 관련 사용자의 공개 정보를 저장합니다.

 

8. 요약

    1.이 기사는 주로 음식 웹 사이트의 데이터 수집 및 저장 과정을 소개하고 웹 페이지 구조, 크롤링 전략, 웹 사이트 유형, 계층 적 관계, 크롤링 방법 및 데이터 저장 과정을 분석하고 마지막으로 각 의견을 실현하는 방법을 자세히 설명합니다. 데이터베이스에 크롤링을 게시하고 데이터를 업데이트하여 반복 크롤링, 크롤링 방지 등을 방지 할 수 있도록 건조 상품이 가득 찼습니다.

    2. 일반적으로이 프로젝트는 그다지 어렵지 않습니다. 아이디어가 옳고 데이터 규칙이있는 한 일어나기 쉽다고 할 수 있습니다. 난이도는 이전에 프로세스가 완료되지 않았고, 상대적으로 물에 대한 소개로 여러분을 도울 수 있기를 바랍니다. 이것이 저의 가장 큰 영광이 될 것입니다.

    3. 문제가 생겼을 때 가장 먼저 생각해야 할 것은 동료, 친구, 선생님에게 물어 보는 것이 아니라 구글과 바이두에 가서 비슷한 상황이 있는지 확인하고 다른 사람들의 경험을 보는 것입니다. 생각하고 문제를 스스로 해결하십시오. 그 후 일이 매우 도움이됩니다. (학교를 떠나지 않았다는 말, 즉 동료에게 물어보고 싶다고합니다.) 온라인에서 특정 정보를 확인한 후에도 여전히 단서가 없어서 다른 사람에게 물어 보면 다른 사람이 더 기꺼이 도와 줄 것입니다 ~

 

저는 여전히 제가 직접 만든 Python 학습 그룹 인 721195303 을 추천하고 싶습니다 . 모두 Python을 배우고 있습니다. Python을 배우고 싶거나 배우고 싶다면 가입 할 수 있습니다. 모두가 소프트웨어 개발 파티이며 수시로 (Python 소프트웨어 개발과 만 관련됨) 최신 Python 고급 자료 및 2021 년에 직접 컴파일 한 제로 기반 교육을 포함합니다. Python에 관심이 있고 고급에 관심이있는 친구를 환영합니다!

추천

출처blog.csdn.net/pyjishu/article/details/114652698