[爬虫]使用selenium爬取豆瓣电影搜索结果,并存入MongoDB数据库中

GitHub地址:https://github.com/al2ln44edr/spider_douban_selenium_mongodb

1、前言

豆瓣电影(https://movie.douban.com/),是豆瓣网站的重要板块之一,存有大量电影信息简介。

本文所记述的内容是,使用selenium爬取豆瓣电影搜索结果,并存入MongoDB数据库中。

1.1.爬取对象

  • 电影名;

  • 演员名录;

  • 详情页链接;

  • 电影时长;

  • 评分;

1.2.使用工具

  • selenium;

  • MongoDB;

2、编码过程

2.1.目标网页分析

第一步,打开豆瓣电影网站,打开开发者模式,查找并获得【搜索框】、【搜索图标】按钮的element元素位置;

第二步,在搜索框中输入【科幻】字样,得到搜索结果页,并获得URL;

第三步,在开发者模式中,找到网页底部“当前页”、【下一页】的element元素位置;

注意:

– 这里本人为了图省事,直接采用点击网页【下一页】的方法、跳转到下一页。

– 在多线程或者并发环境下,需要小伙伴使用其他方法构造URL,比如变换页码、查看URL变化规律,使用“首页URL+页码对应的值”构造URL。同时,还要注意页码的上限值是多少,在构造URL时设置URL数量的上限。

2.2.定义【搜索】模块


from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from lxml import etree

browser = webdriver.Chrome()
# 设置等待时间为10s,超时则会报错
wait    = WebDriverWait(browser,10)
browser.get('https://movie.douban.com/')
# 可以在terminal命令行输入搜索内容
word = input('请输入您要搜索的内容>>> ')
def search():
    # 等待输入框加载完毕
    input = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR,'#inp-query'))
    )
    # 等待【搜索】按钮标签加载完毕
    submit = wait.until(
    EC.element_to_be_clickable((By.CSS_SELECTOR,'#db-nav-movie > div.nav-wrap > div >     div.nav-search > form > fieldset > div.inp-btn > input[type="submit"]'))
    )
    print('输入搜索的内容【{}】'.format(word))
    # 输入搜索内容
    input.send_keys('{}'.format(word))
    # 点击确认
    submit.click()
    # 等待高亮页码加载完毕,表示该网页加载完
    active = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR,'a.num.activate.thispage'))
    )
    print('加载第【{}】页成功'.format(active.text))
    print(browser.page_source)

# 定义主程序

def main():
    search()

if __name__ == '__main__':

    main()

运行,得到下图所示内容,代码OK。

《[爬虫]使用selenium爬取豆瓣电影搜索结果,并存入MongoDB数据库中》 image

2.3.定义翻页模块

from ...
import time

... 

def next_page():
    # 等待【下一页】按钮标签加载完毕,这个主要是确保下一页按加载完毕确实可用,目的是提高爬虫健壮性,不喜欢的话可以使用next_page_submit = browser.find_element_by_css_selector('a.next')代替)

    next_page_submit = wait.until(
    EC.element_to_be_clickable((By.CSS_SELECTOR,'a.next'))
    )
    # 点击【下一页】
    next_page_submit.click()
    # 等待高亮当前页标签加载完毕
    wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR,'a.num.activate.thispage'))
    )
    print('成功加载该页数据!')
    # 打印分页符,主要目的是为了区分不同页的返回结果,不喜欢可删除
    print('--------------加载完成,并打印成功,开始加载下一页------------')
    # 设置睡眠时间为3s
    time.sleep(3)
    # 递归调用,循环获取下一页
    next_page()

# 定义主程序
def main():
    ...
    # 主程序中添加next_page()模块
    next_page()

if __name__ == '__main__':
    main()

运行,得到下图内容,代码OK!

《[爬虫]使用selenium爬取豆瓣电影搜索结果,并存入MongoDB数据库中》 image

2.4.定义网页解析模块,获取网页内容

from ...

import re

... 

def search():
    ...

    # 注意: 由于翻页使用的是递归调用,需要在搜索完成得到第一页时,调用get_movies()模块,以获取第一页解析数据*
    get_movies()

def next_page():
    ...
    # 注意:由于翻页使用的是递归调用,需要在翻页动作完成后,调用get_movies()模块,以获取第二页及之后网页的解析数据*
    get_movies()

def get_movies():
    print('正在解析网页...')
    page = browser.page_source
    selector = etree.HTML(page)
    print('开始打印输出电影信息...')
    # 获取主div模块
    items = selector.xpath('//*[@id="root"]/div/div[2]/div[1]/div[1]')
    for item in items:
        # 获取电影姓名
        names = item.xpath('div/div/div/div[1]/a/text()')
        # 获取电影详情页URL
        urls = item.xpath('div/div/div/div[1]/a/@href')
        # 获取电影评分
        ratings = item.xpath('div/div/div/div[2]/span[2]/text()')
        # 注意:item.xpath()返回的是列表,需要使用str将其字符化*
        durations = re.findall(r'\d\d+',str(item.xpath('div/div/div/div[3]/text()')))
        actors = item.xpath('div/div/div/div[4]/text()')
        注意:由于xpath返回的是列表格式,而我们需要将列表中的元素一一对应存放至字典中,这就需要使用zip()函数,将内容存放至空字典中*
        for name,url,rating,duration,actor in zip(names,urls,ratings,durations,actors):
            # 创建单条电影信息为一个字典,并以键值对形式赋值
            movie_info = {}
            movie_info['name'] = name
            movie_info['url'] = url
            # 如果电影未上映,网页中评分所在element无数值,则需要赋值为None,否则会报错
            if rating == '(尚未上映)' or '(暂无评分)':
                movie_info['rating'] = None
            else:
                movie_info['rating'] = float(rating)
            movie_info['duration'] = int(duration)
            movie_info['actors'] = actor
            print(movie_info)

def main():
    ...

if __name__ == '__main__':
    main()

运行,得到如图结果,代码OK!

《[爬虫]使用selenium爬取豆瓣电影搜索结果,并存入MongoDB数据库中》 image

2.4.完成MongoDB数据库操作

首先,在MongoDB的client中创建名为【doubandianying】的DB;

其次,在【doubandianying】中创建名为【movie_info】的collection;

再次,定义存储到MongoDB模块;

...

# 由于使用递归翻页,所以在get_movies()模块中添加save_to_mongo()模块功能
def get_movies():
    ...
    save_to_mongo(movie_info)

# 定义存储模块,并添加result作为参数
def save_to_mongo(result):
    if db[MONGO_TABLE].insert_one(result):
    print('成功存储到MONGODB')

...

这步比较简单,不再运行。

2.5.爬虫功能优化

首先,为提高爬虫健壮性,添加 try … except … 相关内容;

其次,对于sear()和next_page()模块,如遇加载超时情况,则递归调用本模块,以提高健壮性;

再次,增加爬虫时间计算功能。

3.完整代码

# -*- coding:utf-8 -*-

from selenium import webdriver
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import re
from selenium.common.exceptions import TimeoutException
from config import *
from lxml import etree
import pymongo
import datetime

client = pymongo.MongoClient(MONGO_URL)
db    = client[MONGO_DB]
MONGO_URL = 'localhost'
MONGO_DB    = 'doubandianying'
MONGO_TABLE = 'movie_info'

browser = webdriver.Chrome()
wait    = WebDriverWait(browser,10)
browser.get('https://movie.douban.com/')
word = input('请输入您要搜索的内容>>> ')

def search():
    try:
        input = wait.until(
            EC.presence_of_element_located((By.CSS_SELECTOR,'#inp-query'))
            )
        submit = wait.until(
            EC.element_to_be_clickable((By.CSS_SELECTOR,'#db-nav-movie > div.nav-wrap > div > div.nav-search > form > fieldset > div.inp-btn > input[type="submit"]'))
            )
        print('输入搜索的内容【{}】'.format(word))
        input.send_keys('{}'.format(word))
        submit.click()
        print('正在加载')
        active = wait.until(
            EC.presence_of_element_located((By.CSS_SELECTOR,'a.num.activate.thispage'))
            )
        print('加载第【{}】页成功'.format(active.text))
        get_movies()

except TimeoutException:
    print('等待超时,重新搜索...')
    return search()

def next_page():
    try:
        next_page_submit = wait.until(
            EC.element_to_be_clickable((By.CSS_SELECTOR,'a.next'))
            )
        next_page_submit.click()
        wait.until(
            EC.presence_of_element_located((By.CSS_SELECTOR,'a.num.activate.thispage'))
            )
        print('成功加载该页数据...')
        get_movies()
        print('--------------加载完成,并打印成功,开始加载下一页------------')
        time.sleep(3)
        next_page()
    except TimeoutException:
        print('加载超时,重新加载...')
        return next_page()

def get_movies():
    try:
        print('正在解析...')
        page = browser.page_source
        selector = etree.HTML(page)
        print('开始打印输出电影信息...')
        items = selector.xpath('//*[@id="root"]/div/div[2]/div[1]/div[1]')
        for item in items:
            names = item.xpath('div/div/div/div[1]/a/text()')
            urls = item.xpath('div/div/div/div[1]/a/@href')
            ratings = item.xpath('div/div/div/div[2]/span[2]/text()')
            durations = re.findall(r'\d\d+',str(item.xpath('div/div/div/div[3]/text()')))
            actors = item.xpath('div/div/div/div[4]/text()')
            for name,url,rating,duration,actor in zip(names,urls,ratings,durations,actors):
                movie_info = {}
                movie_info['name'] = name
                movie_info['url'] = url
                if rating == '(尚未上映)' or '(暂无评分)':
                    movie_info['rating'] = None
                else:
                    movie_info['rating'] = float(rating)
            movie_info['duration'] = int(duration)
            movie_info['actors'] = actor
            print(movie_info)
            save_to_mongo(movie_info)
    
    except Exception as e:
        print(e)
        time.sleep(3)
        return get_movies()

def save_to_mongo(result):
    try:
        if db[MONGO_TABLE].insert_one(result):
            print('成功存储到MONGODB')
    except Exception as e:
        raise e

def main():
    start_time = datetime.datetime.now()
    try:
        search()
        next_page()
    except Exception as e:
        raise e
    finally:
        browser.close()
    end_time = datetime.datetime.now()
    print('开始时间:',start_time)
    print('结束时间:',end_time)

if __name__ == '__main__':
    main()

4.后记

该爬虫是入门级爬虫,爬取的是搜索框返回的一级页面的信息;

该爬虫属单进程\单线程爬虫,消耗时间较长,效率不高;

    原文作者:猎户座alpha
    原文地址: https://www.jianshu.com/p/3df95dcbd92f
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞