案例学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('&nbsp;','',item[3])
        info = re.sub('&nbsp;','',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

 

posted @ 2018-11-20 12:26  斑马森林  阅读(592)  评论(0编辑  收藏  举报