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。
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!
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!
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.后记
该爬虫是入门级爬虫,爬取的是搜索框返回的一级页面的信息;
该爬虫属单进程\单线程爬虫,消耗时间较长,效率不高;