
Python爬取豆瓣电影中国医生,爬虫之路,永无止境(附源码)
用Python爬取中国医生电影评论生成词云并实现数据可视化(内附源码)
今日目标:爬取豆瓣电影中国医生评论实现数据可视化
前言
前段时间,主旋律电影《中国医生》上线,大家有没有去看呢?反正我去看了,有点感人(其实哭得稀里哗啦)。看完后,内心感概万分,回到家缓了好久。看了电影的海报画面,想着可以爬一爬大家对电影的评论,正好给大家写文了。于是,利用下班时间给大家做了这个爬虫项目。废话不多了,直接开整。
工具使用
开发环境
系统:Windows10 64位
Python版本:Python3.7
IDE:Pycharm
第三方库:selenium lxml re wordcloud PIL numpy jieba matplotlib
代码演示
饼图
词云生成
柱状图生成
项目思路分析
1 获取网址规律
可以看到 start 和 status是变化的关键
在这个页面可以通过xpath获取地址网页 评论详情 和评分
地址网页:
评论详情:
评分 :
2 打开地址网页,获取地址信息
有些没有地址信息 所有需要获取全部 后期通过数据处理获取有地址信息的
3 把获取的数据进行解析 分别存放三个文件
4 通过读取相应的文件生成相应的图表
项目难点分析
1 滑块验证
这个参考代码里面的滑块验证的方法
通过像素对比找到缺口
移动一段距离 之后速度变化慢慢往前面走 到达缺口就能够验证成功
2 切换iframe
# 切换iframe
driver.switch_to.frame(1)
需要切换iframe才能找到滑块和输入账号 密码 的元素位置
3 显示等待
wait.until(
EC.presence_of_element_located((By.XPATH, '//*[@id="profile"]/div/div[2]'))
)
等待元素出现才进行下一步的处理
4 数据处理
出现不合法的数据 比如 该用户已经主动注销帐号
判断源代码中是否存在 如果存在就跳过 防止等待时间过长 退出程序
if driver.page_source.__contains__("`该用户已经主动注销帐号`"):continue
5 抓取思维
采取先抓大再抓小
这样可以确定几个元素是相对应的
循环的时候会再次使用xpath 此时的xpath写法
需要加一个 .
item = {
"href": email_detail.xpath('./div/a/@href'),
"detail": email_detail.xpath("./div[2]/p/span//text()"),
"rate": email_detail.xpath("./div[2]/h3/span[2]/span[2]/@class")
}
6 json模块默认编码
默认:ascii码 为了中文的正确显示 需要传递参数ensure_ascii
f.write(json.dumps(email, ensure_ascii=False) + ",
")
7 词云图的生成
按照图片格式只需要添加一个参数即可mask
# graph 图片对象
word_cloud = WordCloud(font_path="simsun.ttc",
background_color="white",
mask=graph, # 指定词云的形状
)
8 柱状图和饼图
难点在于json文件读取
循环获取的data 然后通过列表构建echarts需要的数据格式
$.getJSON("json_file/rate.json", function(data) {
var x_data = [];
for (var i = 0; i < data.length; i++) {
console.log(data[i]);
for(var key in data[i]){ // 输出字典元素,如果字典的key是数字,输出时会自动按序输出
hello = {value: data[i][key], name: key+"星"}
x_data[i] = hello;
}
};
源码展示
词云图
# 词云生成
import jieba
import numpy as np
from wordcloud import WordCloud
import matplotlib.pyplot as plt
from PIL import Image
text = open("json_file/detail.text",encoding='utf8').read()
text = text.replace('
',"").replace("u3000","")
text_cut = jieba.lcut(text)
text_cut = ' '.join(text_cut)
# 主要区别
background = Image.open("image/mmexport1626868510673.jpg")
graph = np.array(background)
word_cloud = WordCloud(font_path="simsun.ttc",
background_color="white",
mask=graph, # 指定词云的形状
)
word_cloud.generate(text_cut)
plt.subplots(figsize=(12,8))
plt.imshow(word_cloud)
plt.axis("off")
plt.show()
滑块验证
def slide(driver):
"""滑动验证码"""
# 切换iframe
driver.switch_to.frame(1)
# 找到滑块
block = driver.find_element_by_xpath('//*[@id="tcaptcha_drag_button"]')
# 找到刷新
reload = driver.find_element_by_xpath('//*[@id="reload"]')
while True:
# 摁下滑块
ActionChains(driver).click_and_hold(block).perform()
# 移动
ActionChains(driver).move_by_offset(180, 0).perform()
# 获取位移
tracks = get_tracks(30)
# 循环
for track in tracks:
# 移动
ActionChains(driver).move_by_offset(track, 0).perform()
# 释放
ActionChains(driver).release().perform()
# 停一下
time.sleep(2)
# 判断
if driver.title == "登录豆瓣":
print("失败...再来一次...")
# 单击刷新按钮刷新
reload.click()
# 停一下
time.sleep(2)
else:
break
打开文件获取清洗数据
def get_file():
# 打开文件获取清洗数据
f = open('data.json', 'r', encoding="utf-8")
hello = f.read()
# 地址列表 评分列表 评论内容列表
address_ = []
rate_ = []
detail_ = []
# 按换行符进行
for i in hello.split("
"):
try:
# 转换为字典
data = eval(i[:len(i) - 1])
# 获取评论详细内容
detail = data.get("detail")[0]
# 获取评分
rate = re.findall("(d{1})d", data.get("rate")[0])[0]
# 获取地址
address = ".".join(re.findall("常居: ([u4e00-u9fa5]{2})", "".join(data.get("address"))))
# 地址存在则添加到地址列表中
if address:
address_.append(address)
# 把评分添加到评分列表中
rate_.append(rate)
# 把评论添加到评论列表中
detail_.append(detail)
except:
continue
获取评分和唯一地址
# 获取评分和地址的唯一
rate_set = set(rate_)
address_set = set(address_)
# 写入 评分json
with open("json_file/rate.json", 'w', encoding="utf-8") as file:
rate_json_list = []
for r in rate_set:
# 获取评分频次并且构造字典
rate_json = {r: rate_.count(r)}
rate_json_list.append(rate_json)
file.write(json.dumps(rate_json_list, ensure_ascii=False))
写入地址json文件
# 写入地址json文件
with open("json_file/address.json", 'w', encoding="utf-8") as file:
address_json_list = []
for a in address_set:
# 获取地址频次并且构造字典
address_json = {a: address_.count(a)}
address_json_list.append(address_json)
file.write(json.dumps(address_json_list, ensure_ascii=False))
写入详情文件夹
# 写入详情文件夹
with open("json_file/detail.text", 'w', encoding="utf-8") as file:
detail = "".join(detail_).replace("
", "")
file.write(detail)
def get_json(data, driver, f):
Html = etree.HTML(data)
# 通过xpath获取响应的板块
email_details = Html.xpath('//*[@id="comments"]/div')
items = []
# 循环板块 获取每一个板块里面的数据
for email_detail in email_details:
# 地址页的地址 评论内容 评分呢
item = {
"href": email_detail.xpath('./div/a/@href'),
"detail": email_detail.xpath("./div[2]/p/span//text()"),
"rate": email_detail.xpath("./div[2]/h3/span[2]/span[2]/@class")
}
items.append(item)
循环列表获取地址网址
# 循环列表获取地址网址 爬取地址
for email in items:
# 如果网址为控则跳过
if not email.get("href"):
continue
driver.get(email.get("href")[0])
# 如果评论用户已经注册则跳过
if driver.page_source.__contains__("该用户已经主动注销帐号"):
continue
print(email.get("href"))
wait = WebDriverWait(driver, 10)
# 隐士等待 等待数据板块出现进行下一步
wait.until(
EC.presence_of_element_located((By.XPATH, '//*[@id="profile"]/div/div[2]'))
)
# 获取地址
email["address"] = etree.HTML(driver.page_source).xpath('//*[@id="profile"]/div/div[2]/div[1]/div//text()')
# 整体数据写入初始文件中
f.write(json.dumps(email, ensure_ascii=False) + ",
")
主程序
def main(user, pwd):
"""主程序"""
f = open("data.json", "w", encoding="utf-8")
f.write("[")
url = "https://accounts.douban.com/passport/login"
# 驱动路径
driver_file = os.path.join(os.path.dirname(__file__), "driver_exe", "chromedriver.exe")
# 声明驱动
driver = webdriver.Chrome(executable_path=driver_file)
driver.get(url)
driver.find_element_by_xpath('//*[@id="account"]/div[2]/div[2]/div/div[1]/ul[1]/li[2]').click()
driver.find_element_by_xpath('//*[@id="username"]').send_keys(user)
driver.find_element_by_xpath('//*[@id="password"]').send_keys(pwd)
driver.find_element_by_xpath('//*[@id="account"]/div[2]/div[2]/div/div[2]/div[1]/div[4]/a').click()
# 停一下,等待出现
time.sleep(2)
# 滑动验证码
slide(driver)
print("成功")
type_mile = ["P", "F"]
for type_ in type_mile:
if type_mile == "P":
index_mile = 480
else:
index_mile = 180
for i in range(0, index_mile, 20):
url = f"https://movie.douban.com/subject/35087699/comments?start={i}&limit=20&status={type_}&sort=new_score"
print(url)
driver.get(url)
# 解析数据
get_json(driver.page_source, driver, f)
f.write("]")
driver.quit()
get_file()
# 所有数据获取完毕之后推出浏览器 解析数据
# 解析完毕并且创建解析之后数据的json文件
f.close()
if __name__ == '__main__':
user = input("请输入你的账号:")
pwd = input("请输入你的密码:")
main(user, pwd)
仅供学习,爬虫使用需谨慎!
希望可以得到各位的一键三连,感谢各位支持!
祝大家学习python顺利!
如果有正在跟我一样的自学的朋友,需要我本篇的代码或者其他的Python学习资料可以转发此文私信我(私信发我“中国医生”)