diff --git a/crawl top_250 douban.movie.py b/crawl top_250 douban.movie.py new file mode 100644 index 0000000..c3d6198 --- /dev/null +++ b/crawl top_250 douban.movie.py @@ -0,0 +1,238 @@ +from bs4 import BeautifulSoup # 网页解析,获取数据 +import re # 正则表达式,进行文字匹配` +import urllib.request, urllib.error # 制定URL,获取网页数据 +import xlwt # 进行excel操作 +import sqlite3 # 进行SQLite数据库操作 +from concurrent.futures import ThreadPoolExecutor #优化,使用了异步IO和线程池,可以提高程序的并发处理能力和性能。 +import asyncio #使用了异步IO和线程池,可以提高程序的并发处理能力和性能 +import matplotlib.pyplot as plt +import numpy as np +from tqdm import tqdm + + +findLink = re.compile(r'') # 创建正则表达式对象,标售规则 影片详情链接的规则 +findImgSrc = re.compile(r'(.*)') +findRating = re.compile(r'(.*)') +findJudge = re.compile(r'(\d*)人评价') +findInq = re.compile(r'(.*)') +findBd = re.compile(r'

(.*?)

', re.S) + + +# 得到指定一个URL的网页内容,反爬手段 +def askURL(url): + head = { # 模拟浏览器头部信息,向豆瓣服务器发送消息 + "User-Agent": "Mozilla / 5.0(Windows NT 10.0; Win64; x64) AppleWebKit / 537.36(KHTML, like Gecko) Chrome / 80.0.3987.122 Safari / 537.36" + } + request = urllib.request.Request(url, headers=head) + html = "" + try: + # 用户代理,表示告诉豆瓣服务器,我们是什么类型的机器、浏览器(本质上是告诉浏览器,我们可以接收什么水平的文件内容) + response = urllib.request.urlopen(request) + html = response.read().decode("utf-8") + except urllib.error.URLError as e:#捕获 URLError 异常,并将其命名为 e + if hasattr(e, "code"): #查异常对象 e 是否包含 code 属性,用于打印错误代码和原因(如果存在) + print(e.code) + if hasattr(e, "reason"): #查异常对象 e 是否包含reason 属性,用于打印错误代码和原因(如果存在)。 + print(e.reason) + return html + +#获取网页 +def parse_page(html): + soup = BeautifulSoup(html, "html.parser") #用于解析 HTML 内容,Python 的内置解析器 html.parse + data_list = [] #用来存储爬取的网页信息 + for item in soup.find_all('div', class_="item"):# 查找符合要求的字符串 + data = [] + item = str(item) + link = re.findall(findLink, item)[0] # 通过正则表达式查找 + data.append(link) + img_src = re.findall(findImgSrc, item)[0] + data.append(img_src) + titles = re.findall(findTitle, item) #查找名称 + if len(titles) == 2: + c_title = titles[0] + data.append(c_title) + o_title = titles[1].replace("/", "") #消除转义字符 + data.append(o_title) + else: + data.append(titles[0]) + data.append(' ') + rating = re.findall(findRating, item)[0] #查找评分 + data.append(rating) + judge_num = re.findall(findJudge, item)[0] #查找评论 + data.append(judge_num) + inq = re.findall(findInq, item) #查找概况 + if len(inq) != 0: + inq = inq[0].replace("。", "") + data.append(inq) + else: + data.append(" ") + bd = re.findall(findBd, item)[0] #查找相关信息 + bd = re.sub('(\s+)?', "", bd)#替换 bd 中的
标签及其周围的空白字符为空字符串,以删除换行符 + bd = re.sub('/', "", bd) #将 bd 中的斜杠 / 替换为空字符串 + data.append(bd.strip()) + data_list.append(data) + return data_list + + +#可视化功能 + +def visualize(ranting): + # 定义评分区间 + bins = [7,8.5, 9, 9.5, 10] + + # 使用 numpy.histogram() 计算每个评分区间内的电影数量 + counts, _ = np.histogram(ranting, bins=bins) + + # 定义每个区间的标签 + labels = ['7-8.5', '8.5-9', '9-9.5', '9.5-10'] + + # 绘制饼图 + plt.figure(figsize=(8, 8)) #创建一个图形窗口,设置其大小为 8x8 英寸 + #绘制饼图。counts 是各个评分的数量,labels 是评分的标签,autopct='%1.1f%%' 设置显示百分比,并保留一位小数, + # startangle=140 设置起始角度为 140 度。 + plt.pie(counts, labels=labels, autopct='%1.1f%%', startangle=140) + plt.axis('equal') # 保证饼图是圆形而不是椭圆形 + plt.title('Distribution of Movie Ratings') #设置图形的标题 + plt.tight_layout() #自动调整子图参数,确保图形布局美观。 + plt.show() + + +# 保存数据到表格 +def saveData(datalist, savepath): + print("Saving data...") + book = xlwt.Workbook(encoding="utf-8", style_compression=0) #创建一个新的 Excel 工作簿 + sheet = book.add_sheet('豆瓣电影Top250', cell_overwrite_ok=True) #来添加一个新的工作表到工作簿中 + col = ["电影详情链接", "图片链接", "影片中文名", "影片外国名", "评分", "评价数", "概况", "导演", "主演", "年份", "国家", "类型"] + + # 写入列名 + for i, column_name in enumerate(col): + sheet.write(0, i, column_name) #向特定的行和列写入数据 + + # 写入数据 + for row_index, movie_info in enumerate(datalist, start=1):#start=1意味着索引从 1 开始,这样可以避免覆盖 Excel 表格的第一行 + for col_index, info in enumerate(movie_info): + if isinstance(info, list): # 检查 info 是否是一个列表 + for sub_index, sub_info in enumerate(info): + sheet.write(row_index, col_index + sub_index, sub_info) #确保子信息不会覆盖主信息,因为子列表中的内容将从当前列开始逐个写入,按序排列 + else: + sheet.write(row_index, col_index, info) + + book.save(savepath) + + +#保存到数据库,创建数据库 +def init_db(dbpath): + sql = ''' + create table movie250( + id integer primary key autoincrement, + info_link text, + pic_link text, + cname varchar, + ename varchar , + score numeric, + rated numeric, + instroduction text, + info text + ) + + + ''' #创建数据表 + conn = sqlite3.connect(dbpath) + cursor = conn.cursor() + cursor.execute(sql) + conn.commit() + conn.close() + +#将数据保存到数据库 +def saveData2DB(datalist,dbpath): + init_db(dbpath) + conn = sqlite3.connect(dbpath) #函数来连接到 SQLite 数据库文件 + cur = conn.cursor() #创建一个游标对象。游标对象用于执行 SQL 查询和获取结果。 + for data in datalist: + for index in range(len(data)): + if index == 4 or index == 5: #跳过该索引位置,因为在数据库表格中并不需要这些信息。 + continue + data[index] = '"'+data[index]+'"' + sql = ''' + insert into movie250( + info_link,pic_link,cname,ename,score,rated,instroduction,info) + values (%s)'''%",".join(data) #将列表 data 中的值通过逗号连接为一个字符串 + #print(sql) #输出查询语句,用来测试 + cur.execute(sql) #execute() 方法来执行 SQL 插入 + conn.commit() + cur.close #关闭数据库 + conn.close() + +#查询数据库中的数据,查询数据库中的电影大于9分的电影 +def search(): + def execute_query(query, db_path): + conn = sqlite3.connect(db_path) #连接数据库 + cursor = conn.cursor() #连接对象的 cursor() 方法来创建一个游标对象。游标对象用于执行 SQL 查询和获取结果 + cursor.execute(query) #execute() 方法来执行 SQL 查询 + rows = cursor.fetchall() #SELECT 查询,使用游标对象的fetchall()方法来获取查询结果 + conn.close() + return rows + + # 示例查询函数的使用 + query = "SELECT * FROM movie250 WHERE score > 9.0" + db_path = "movie.db" + result = execute_query(query, db_path) + for row in result: + print(row) + + +async def main(): + baseurl = "https://movie.douban.com/top250?start=" # 要爬取的网页链接 + urls = [baseurl + str(i) for i in range(0, 250, 25)] # 构建URL列表 + data=[0]*250 + data=await main_async(urls) # 调用异步主函数获取数据 + datas = [item for sublist in data for item in sublist] # 使用 flatten() 方法将二维数组拆分成一维 + choice=input('请选择存储方式:a:excel表,b:数据库') + if choice=='a': + savepath = "豆瓣电影Top250.xls" # 当前目录新建XLS,存储进去 + # 等待异步操作完成后执行保存操作 + await asyncio.sleep(3) # 使用异步等待来等待3秒钟 + saveData(datas, savepath) # 使用excel表来保存 + elif choice=='b': + dbpath = "movie.db" #当前目录新建数据库,存储进去 + # 等待异步操作完成后执行保存操作 + await asyncio.sleep(3) # 使用异步等待来等待3秒钟 + saveData2DB(datas,dbpath) #使用数据库保存 + search() #查询数据库里的数据 + + else: + print('请选择a或者b') + + # 假设这里是异步数据获取的部分,等待异步操作完成 + + ratings = [float(movie[4]) for movie in datas] #提取电影评分 + visualize(ratings) #可视化功能 + + print("爬取完毕!") + + +# 这是异步数据获取的函数 +async def main_async(urls): + # 使用线程池执行异步任务 + with ThreadPoolExecutor(max_workers=5) as executor: + loop = asyncio.get_event_loop() + # 将每个 URL 的请求任务加入到事件循环中 + tasks = [loop.run_in_executor(executor, askURL, url) for url in urls] + # 等待所有请求任务完成 + htmls = await asyncio.gather(*tasks) + # 解析每个页面的 HTML 内容 + data = [parse_page(html) for html in htmls] + + # 使用tqdm动画效果显示爬取进度 + for _ in tqdm(range(len(urls)), desc="正在爬取网页", unit="页"): + await asyncio.sleep(0.1) # 模拟爬取网页的耗时 + + return data + + +if __name__ == "__main__": + # 运行主函数 + asyncio.run(main()) + +