案例学python——案例三:豆瓣电影信息入库
闲扯皮
昨晚给高中的妹妹微信讲题,函数题,小姑娘都十二点了还迷迷糊糊。今天凌晨三点多,被连续的警报声给惊醒了,以为上海拉了防空警报,难不成地震,空袭?难道是楼下那个车主车子被堵了,长按喇叭?开窗看看,好像都不是。好鬼畜的警报声,家里也没装报警器啊,莫不成家里煤气漏了?起床循声而查,报警声的确在厨房,听起来也像屋外,开门也没发现啥异样,莫不成真的是煤气表?下面开始排查,开水,断水,发现没啥异样。打开煤气灶,关闭煤气,也没啥。全屋断电也没啥,全屋都断电了只能说报警声的确来自煤气表。翻出燃气公司的客服电话,那头真的是个妹子,电话里报了下情况和地址,燃气公司说四个小时内给回访。半个小时后警报声自动消失了,一个小时后门铃声响,维修工拿着测气表一通测试,反馈结果说煤气表是新的,也没有明处漏气,让我签了字,就撤了。打开窗,埋头继续睡了。复盘发现自己犯了个大错,当时不应该打开煤气灶和热水器测试的燃气的,万一煤气真的泄露,后果,今天估计医院凉凉了,不对应该是焦焦了。家庭安全不可大意,还是太年轻了。早上6点多楼上装修的工人拆墙砸墙的声音,不胜其扰,早点去公司敲代码吧。先是复盘了一下昨天爬豆瓣的一些小问题,顺带简单解决了。不闲扯了,代码复盘Python爬豆瓣Top250 的信息入库。
案例一尝试了爬图之后的快感,案例二尝试了白傻呆的数据库操作,案例三就两者整合一下。起因是昨天刚在博客园看到 一篇爬豆瓣的文章 想着刚好能把文章中爬到的信息入数据库,如果在用java操作数据库
岂不美哉,原谅我习惯于javaWeb开发,因为目前只会java啊。昨天用的Python2.7着实不爽,装了3.7版本。
效果预览:
效果一:项目结构
效果二:数据库信息
效果三:本地存储
思路:第一步:爬取信息 第二步:信息解析 第三步:读写文件 第四步:解析数据入库
准备工作
根据解析字段建立对应的数据库,这点因为 博客:一起学爬虫——通过爬取豆瓣电影top250学习requests库的使用 中已经可以看到爬取后解析相关字段,可能有些字段颗粒度不够,在原有基础上再切割切割就ok啦。
数据库连接配置
dbMysqlConfig.cnf
[dbMysql]
host = localhost
port = 3306
user = root
password = root
db_name = dou_film
封装的DBUtils,中间有些小白的错,在里面栽了几个坑:事务,提交,主键自增啥的。其实就用了一个insert()方法,其他方法的正确性可忽略。
mysqlDBUtils.py
#!/usr/bin/python3 # -*- coding:utf-8 -*- import pymysql, os, configparser from pymysql.cursors import DictCursor from DBUtils.PooledDB import PooledDB class Config(object): """ # Config().get_content("user_information") 配置文件里面的参数 [dbMysql] host = 192.168.1.101 port = 3306 user = root password = python123 """ def __init__(self, config_filename="dbMysqlConfig.cnf"): file_path = os.path.join(os.path.dirname(__file__), config_filename) self.cf = configparser.ConfigParser() self.cf.read(file_path) def get_sections(self): return self.cf.sections() def get_options(self, section): return self.cf.options(section) def get_content(self, section): result = {} for option in self.get_options(section): value = self.cf.get(section, option) result[option] = int(value) if value.isdigit() else value return result class BasePymysqlPool(object): def __init__(self, host, port, user, password, db_name): self.db_host = host self.db_port = int(port) self.user = user self.password = str(password) self.db = db_name self.conn = None self.cursor = None class MyPymysqlPool(BasePymysqlPool): """ MYSQL数据库对象,负责产生数据库连接 , 此类中的连接采用连接池实现 获取连接对象:conn = Mysql.getConn() 释放连接对象;conn.close()或del conn """ # 连接池对象 __pool = None def __init__(self, conf_name=None): self.conf = Config().get_content(conf_name) super(MyPymysqlPool, self).__init__(**self.conf) # 数据库构造函数,从连接池中取出连接,并生成操作游标 self._conn = self.__getConn() self._cursor = self._conn.cursor() def __getConn(self): """ @summary: 静态方法,从连接池中取出连接 @return MySQLdb.connection """ if MyPymysqlPool.__pool is None: __pool = PooledDB(creator=pymysql, mincached=1, maxcached=20, host=self.db_host, port=self.db_port, user=self.user, passwd=self.password, db=self.db, use_unicode=False, charset="utf8", cursorclass=DictCursor) return __pool.connection() def getAll(self, sql, param=None): """ @summary: 执行查询,并取出所有结果集 @param sql:查询SQL,如果有查询条件,请只指定条件列表,并将条件值使用参数[param]传递进来 @param param: 可选参数,条件列表值(元组/列表) @return: result list(字典对象)/boolean 查询到的结果集 """ if param is None: count = self._cursor.execute(sql) else: count = self._cursor.execute(sql, param) if count > 0: result = self._cursor.fetchall() else: result = False return result def getOne(self, sql, param=None): """ @summary: 执行查询,并取出第一条 @param sql:查询SQL,如果有查询条件,请只指定条件列表,并将条件值使用参数[param]传递进来 @param param: 可选参数,条件列表值(元组/列表) @return: result list/boolean 查询到的结果集 """ if param is None: count = self._cursor.execute(sql) else: count = self._cursor.execute(sql, param) if count > 0: result = self._cursor.fetchone() else: result = False return result def getMany(self, sql, num, param=None): """ @summary: 执行查询,并取出num条结果 @param sql:查询SQL,如果有查询条件,请只指定条件列表,并将条件值使用参数[param]传递进来 @param num:取得的结果条数 @param param: 可选参数,条件列表值(元组/列表) @return: result list/boolean 查询到的结果集 """ if param is None: count = self._cursor.execute(sql) else: count = self._cursor.execute(sql, param) if count > 0: result = self._cursor.fetchmany(num) else: result = False return result def insertMany(self, sql, values): """ @summary: 向数据表插入多条记录 @param sql:要插入的SQL格式 @param values:要插入的记录数据tuple(tuple)/list[list] @return: count 受影响的行数 """ count = self._cursor.executemany(sql, values) return count def __query(self, sql, param=None): if param is None: count = self._cursor.execute(sql) else: count = self._cursor.execute(sql, param) return count def update(self, sql, param=None): """ @summary: 更新数据表记录 @param sql: SQL格式及条件,使用(%s,%s) @param param: 要更新的 值 tuple/list @return: count 受影响的行数 """ return self.__query(sql, param) def insert(self, sql, param=None): """ @summary: 更新数据表记录 @param sql: SQL格式及条件,使用(%s,%s) @param param: 要更新的 值 tuple/list @return: count 受影响的行数 """ num = self._cursor.execute(sql) self._conn.commit() return num def delete(self, sql, param=None): """ @summary: 删除数据表记录 @param sql: SQL格式及条件,使用(%s,%s) @param param: 要删除的条件 值 tuple/list @return: count 受影响的行数 """ return self.__query(sql, param) def begin(self): """ @summary: 开启事务 """ self._conn.autocommit(0) def end(self, option='commit'): """ @summary: 结束事务 """ if option == 'commit': self._conn.commit() else: self._conn.rollback() def dispose(self, isEnd=1): """ @summary: 释放连接池资源 """ if isEnd == 1: self.end('commit') else: self.end('rollback') self._cursor.close() self._conn.close() if __name__ == '__main__': mysql = MyPymysqlPool("dbMysql") sqlAll = "select * from seckill;" result = mysql.getAll(sqlAll) print(result) # 释放资源 mysql.dispose()
爬豆瓣页面数据解析,比较佩服的是里面爬取用的是正则表达式去匹配的,这点只能看个门道,平时用正则表达式都是百度匹配的,测试ok就用了。对原有豆瓣做了一些改动,使其合乎我的想法,虽然想法以后回头看也会诸多问题,先搞出来再说吧。
其实导包,类引入,为难了自己一下下。其他都还算顺利。
douFilm.py
#coding=utf-8 import requests import re import json import importlib import os dbUtils = importlib.import_module('mysqlDBUtils') # 定义图片存储位置 global save_path save_path = 'D:/doubanfilm' # 创建文件夹 def createFile(file_path): if os.path.exists(file_path) is False: os.makedirs(file_path) # 切换路径至上面创建的文件夹 os.chdir(file_path) def parse_html(url): headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0"} response = requests.get(url, headers=headers) text = response.text regix = '<div class="pic">.*?<em class="">(.*?)</em>.*?<img.*?src="(.*?)" class="">.*?div class="info.*?class="hd".*?class="title">(.*?)</span>.*?class="other">' \ '(.*?)</span>.*?<div class="bd">.*?<p class="">(.*?)<br>(.*?)</p>.*?class="star.*?<span class="(.*?)"></span>.*?' \ 'span class="rating_num".*?average">(.*?)</span>' results = re.findall(regix, text, re.S) mysql = dbUtils.MyPymysqlPool("dbMysql") for item in results: filepath = down_image(item[1],headers = headers) print("文件路径"+filepath) print(item) # item[2] 电影主流名字 item[3] 电影别名 film_name = item[2] + ' ' + re.sub(' ','',item[3]) info = re.sub(' ','',item[4].strip()).split(":") # 导演 director = info[1].split('主')[0] # 主演 print(len(info)) if len(info) > 2: actor = info[2] else: actor = "..." score_mark = star_transfor(item[6].strip()) + '/' + item[7] + '分' rank_num = item[0] print(film_name) # 写sql 语句 sql = 'insert into film (film_name,director,actor,score_mark,rank_num,filepath) value("' + film_name + '","' + director + '","' + actor + '","' + score_mark + '","' + rank_num + '","'+filepath+'")' # 执行插入 result = mysql.insert(sql) yield { '电影名称' : film_name, '导演和演员' : director, '评分': score_mark, '排名' : rank_num } mysql.dispose() def main(): for offset in range(0, 250, 25): url = 'https://movie.douban.com/top250?start=' + str(offset) +'&filter=' for item in parse_html(url): # 将每个条目写入txt write_movies_file(item) def write_movies_file(str): with open('douban_film.txt','a',encoding='utf-8') as f: f.write(json.dumps(str,ensure_ascii=False) + '\n') def down_image(url,headers): r = requests.get(url,headers = headers) createFile(save_path) filepath = save_path +'/'+ re.search('/public/(.*?)$', url, re.S).group(1) print("下载的海报名字"+filepath) with open(filepath,'wb') as f: f.write(r.content) return filepath def star_transfor(str): if str == 'rating5-t': return '五星' elif str == 'rating45-t' : return '四星半' elif str == 'rating4-t': return '四星' elif str == 'rating35-t' : return '三星半' elif str == 'rating3-t': return '三星' elif str == 'rating25-t': return '两星半' elif str == 'rating2-t': return '两星' elif str == 'rating15-t': return '一星半' elif str == 'rating1-t': return '一星' else: return '无星' if __name__ == '__main__': main()
一切还算顺利,注释都在代码里写明了,应该比较好理解。运行的时候,直接运行douFilm.py就ok了。项目很简单 gitHub地址:https://github.com/islowcity/doufilm.git