第十一章电子商务网站用户行为分析及服务推荐

目录

一、背景

二、数据导入

2.1 创建数据库

 2.2 导入查询

2.3 出现的错误

2.4 解决办法

三、数据抽取

四、数据探索分析

4.1 分析网页类型

 4.2 网页107类型中的内部统计

4.3 统计带"?"问号网址类型统计

4.4 统计199类型中的具体类型占比

4.5 统计瞎逛用户中各个类型占比

4.6 统计用户浏览网页次数的情况 

4.7 分析浏览次数为一次的用户的行为

五、构建智能推荐模型

5.1 数据清洗

5.2 划分IP集合为训练集和测试集

5.3 模型构建

5.4 模型评价

总结


一、背景

        智能推荐服务是提高电子商务网站销售转化率的重要技术手段之一。它与传统的搜索技术有着重要的区别,智能推荐服务能够更加精准地提供信息,节省用户找寻信息的时间,提高找寻信息的准确度。通过建立智能推荐系统提高服务效率,帮助消费者节约时间成本,帮助企业制定有针对性的营销战略方案,促进企业长期、稳定、高速发展。


二、数据导入

  • 找到MySQL安装位置的bin目录(我的是E:\MySQL Server 8.0\bin)
  • 使用快捷键WIN+R,输入cmd打开命令行
  • 切换盘符(如果MySQL安装在C盘则不需要这一步)
  • 切换路径到 E:\MySQL Server 8.0\bin
  • 输入mysql -u root -p ,u字母后面是mysql的登录数据库用户名,我的登录数据库用户名是root,然后回车,会让输入登录mysql数据库的密码

登录mysql

2.1 创建数据库

数据库名称:test 

字符集:utf8mb4

排序规则:utf8mb4 general_ci

1、创建数据库

create database test default character set utf8mb4 collate utf8mb4_general_ci;

2、使用数据库

use test;

 2.2 导入查询

source D:\\My_Data\\chapter11\\demo\\data\\7law.sql

D:\\My_Data\\chapter11\\demo\\data\\7law.sql 是.sql文件存放的位置

注意:这里需要使用双反斜杠,对原有的路径单反斜杠进行转义  

2.3 出现的错误

ERROR 1:

ERROR 1118 (42000): Row size too large (> 8126). Changing some columns to TEXT or BLOB or using ROW_FORMAT=DYNAMIC or ROW_FORMAT=COMPRESSED may help. In current row format, BLOB prefix of 768 bytes is stored inline.

这个错误信息是MySQL数据库报出的错误,它表示在当前的行格式下,某个表的行大小超过了MySQL所允许的最大值8126字节。造成这个问题的原因是表定义了太多的列,或者表中包含了太多的大型数据类型。在MySQL中,BLOB和TEXT类型的数据会被存储在行中,而不是存储在独立的数据页中。因此,如果表中包含太多的大型数据类型,就会导致行大小超过MySQL的限制。

ERROR 2:

ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'Media Center PC 6.0' at line 1
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '.NET4.0C' at line 1
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '.NET4.0E)', 'Windows 7', '1408129230.1422933436', '1408129230.1422933436', 14229' at line 1

这里展示的是语法错误,实际上是由于7law.sql文件创建表的编码(gbk)与数据库编码(utf8)不一致导致的。(注意:使用Navicat Premium导入时会自动处理这个错误,不代表错误不存在)

2.4 解决办法

使用ROW_FORMAT=DYNAMIC或ROW_FORMAT=COMPRESSED行格式,这会将BLOB类型的数据存储在独立的数据页中,从而避免行大小超过MySQL的限制。修改CHARSET=gbk为CHARSET=utf8

原始7law.sql部分数据

-- --------------------------------------------------------
-- 主机:                           127.0.0.1
-- 服务器版本:                        10.0.17-MariaDB - mariadb.org binary distribution
-- 服务器操作系统:                      Win64
-- HeidiSQL 版本:                  9.1.0.4867
-- --------------------------------------------------------

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8mb4 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;

-- 导出  表 new.all_gzdata 结构
DROP TABLE IF EXISTS `all_gzdata`;
CREATE TABLE IF NOT EXISTS `all_gzdata` (
  `realIP` bigint(20) DEFAULT '0' COMMENT '真实ip',
  `realAreacode` int(11) DEFAULT '0' COMMENT '地区编号',
  `userAgent` varchar(500) DEFAULT '' COMMENT '浏览器代理',
  `userOS` varchar(100) DEFAULT '' COMMENT '用户浏览器类型',
  `userID` varchar(100) DEFAULT '' COMMENT 'userid',
  `clientID` varchar(100) DEFAULT '' COMMENT 'clientid',
  `timestamp` bigint(20) DEFAULT '0' COMMENT '时间戳',
  `timestamp_format` varchar(20) DEFAULT '' COMMENT '格式化的时间',
  `pagePath` varchar(1000) DEFAULT '' COMMENT '路径',
  `ymd` int(11) DEFAULT '0',
  `fullURL` varchar(1000) DEFAULT '',
  `fullURLId` varchar(100) DEFAULT '',
  `hostname` varchar(500) DEFAULT '',
  `pageTitle` varchar(1000) DEFAULT '',
  `pageTitleCategoryId` int(11) DEFAULT '0',
  `pageTitleCategoryName` varchar(500) DEFAULT '',
  `pageTitleKw` varchar(1000) DEFAULT '',
  `fullReferrer` varchar(1000) DEFAULT '',
  `fullReferrerURL` varchar(1000) DEFAULT '',
  `organicKeyword` varchar(500) DEFAULT '',
  `source` varchar(500) DEFAULT '',
  KEY `Index 1` (`realAreacode`,`timestamp_format`,`realIP`,`userOS`,`userID`,`clientID`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk ROW_FORMAT=COMPACT;

-- 正在导出表  new.all_gzdata 的数据:~742,859 rows (大约)
/*!40000 ALTER TABLE `all_gzdata` DISABLE KEYS */;
INSERT INTO `all_gzdata` (`realIP`, `realAreacode`, `userAgent`, `userOS`, `userID`, `clientID`, `timestamp`, `timestamp_format`, `pagePath`, `ymd`, `fullURL`, `fullURLId`, `hostname`, `pageTitle`, `pageTitleCategoryId`, `pageTitleCategoryName`, `pageTitleKw`, `fullReferrer`, `fullReferrerURL`, `organicKeyword`, `source`) VALUES
	(2683657840, 140100, 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36 SE 2.X MetaSr 1.0', 'Windows XP', '785022225.1422973265', '785022225.1422973265', 1422973268278, '2015-02-03 22:21:08', '/info/hunyin/hunyinfagui/201404102884290_6.html', 20150203, 'http://www.lawtime.cn/info/hunyin/hunyinfagui/201404102884290_6.html', '107001', 'www.lawtime.cn', '广东省人口与计划生育条例全文2014 - 法律快车婚姻法', 31, '故意伤害', '计划生育', NULL, NULL, NULL, NULL),

 修改后7law.sql部分数据

删除 ENGINE=InnoDB DEFAULT CHARSET=gbk ROW_FORMAT=COMPACT;

添加 ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8mb4 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;

-- 导出  表 new.all_gzdata 结构
DROP TABLE IF EXISTS `all_gzdata`;
CREATE TABLE IF NOT EXISTS `all_gzdata` (
  `realIP` bigint(20) DEFAULT '0' COMMENT '真实ip',
  `realAreacode` int(11) DEFAULT '0' COMMENT '地区编号',
  `userAgent` varchar(500) DEFAULT '' COMMENT '浏览器代理',
  `userOS` varchar(100) DEFAULT '' COMMENT '用户浏览器类型',
  `userID` varchar(100) DEFAULT '' COMMENT 'userid',
  `clientID` varchar(100) DEFAULT '' COMMENT 'clientid',
  `timestamp` bigint(20) DEFAULT '0' COMMENT '时间戳',
  `timestamp_format` varchar(20) DEFAULT '' COMMENT '格式化的时间',
  `pagePath` varchar(1000) DEFAULT '' COMMENT '路径',
  `ymd` int(11) DEFAULT '0',
  `fullURL` varchar(1000) DEFAULT '',
  `fullURLId` varchar(100) DEFAULT '',
  `hostname` varchar(500) DEFAULT '',
  `pageTitle` varchar(1000) DEFAULT '',
  `pageTitleCategoryId` int(11) DEFAULT '0',
  `pageTitleCategoryName` varchar(500) DEFAULT '',
  `pageTitleKw` varchar(1000) DEFAULT '',
  `fullReferrer` varchar(1000) DEFAULT '',
  `fullReferrerURL` varchar(1000) DEFAULT '',
  `organicKeyword` varchar(500) DEFAULT '',
  `source` varchar(500) DEFAULT '',
  KEY `Index 1` (`realAreacode`,`timestamp_format`,`realIP`,`userOS`,`userID`,`clientID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

-- 正在导出表  new.all_gzdata 的数据:~742,859 rows (大约)
/*!40000 ALTER TABLE `all_gzdata` DISABLE KEYS */;
INSERT INTO `all_gzdata` (`realIP`, `realAreacode`, `userAgent`, `userOS`, `userID`, `clientID`, `timestamp`, `timestamp_format`, `pagePath`, `ymd`, `fullURL`, `fullURLId`, `hostname`, `pageTitle`, `pageTitleCategoryId`, `pageTitleCategoryName`, `pageTitleKw`, `fullReferrer`, `fullReferrerURL`, `organicKeyword`, `source`) VALUES
	(2683657840, 140100, 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36 SE 2.X MetaSr 1.0', 'Windows XP', '785022225.1422973265', '785022225.1422973265', 1422973268278, '2015-02-03 22:21:08', '/info/hunyin/hunyinfagui/201404102884290_6.html', 20150203, 'http://www.lawtime.cn/info/hunyin/hunyinfagui/201404102884290_6.html', '107001', 'www.lawtime.cn', '广东省人口与计划生育条例全文2014 - 法律快车婚姻法', 31, '故意伤害', '计划生育', NULL, NULL, NULL, NULL),
	

重新导入数据

source D:\\My_Data\\chapter11\\demo\\data\\7law.sql

 数据信息

mysql> select count(1) from all_gzdata;
+----------+
| count(1) |
+----------+
|   837450 |
+----------+
1 row in set (0.79 sec)

三、数据抽取

# 导入需要用到的库
import time

import pandas as pd
from sqlalchemy import create_engine

# 记录程序开始时间
start_time = time.time()
print("开始时间:", start_time)

# 创建一个 SQLAlchemy 引擎对象,使用 root 用户名和密码连接本地的 test 数据库
engine = create_engine('mysql+pymysql://root:011228@localhost:3306/test?charset=utf8')
# 从指定表格 all_gzdata 中读取数据,每次读取 10000 行数据,并返回一个迭代器
data_iter = pd.read_sql('all_gzdata', engine.connect(), chunksize=10000)

# 遍历迭代器中的每个 DataFrame 对象,依次将它们写入到本地 csv 文件中
for i, df in enumerate(data_iter):
    print(f"正在处理第 {i + 1} 个 DataFrame...")
    df.to_csv(f'../tmp/all_gzdata.csv', index=False, encoding='utf-8', mode='a')

# 计算程序运行时间并输出
print("运行时间:", time.time() - start_time)

以上代码中,使用了 pd.read_sql() 方法的 chunksize 参数,返回了一个迭代器 data_iter,其中包含多个 DataFrame 对象,每个对象中包含 10000 行数据。接下来,使用 for 循环来遍历迭代器中的每个 DataFrame 对象,并将它们依次写入到本地 csv 文件中。在每次写入时,都需要指定 mode='a' 参数,表示以追加模式打开文件,以便将多个 DataFrame 对象的数据都写入到同一个 csv 文件中。


四、数据探索分析

4.1 分析网页类型

import pandas as pd
from sqlalchemy import create_engine

engine = create_engine('mysql+pymysql://root:011228@localhost:3306/test?charset=utf8')
sql = pd.read_sql('all_gzdata', engine.connect(), chunksize=10000)

# 分析网页类型
counts = [i['fullURLId'].value_counts() for i in sql]  # 逐块统计
counts = counts.copy()
counts = pd.concat(counts).groupby(level=0).sum()  # 合并统计结果,把相同的统计项合并(即按index分组并求和)
counts = counts.reset_index()  # 重新设置index,将原来的index作为counts的一列。
counts.columns = ['index', 'num']  # 重新设置列名,主要是第二列,默认为0
counts['type'] = counts['index'].str.extract('(\d{3})')  # 提取前三个数字作为类别id
counts_ = counts[['type', 'num']].groupby('type').sum()  # 按类别合并
counts_.sort_values(by='num', ascending=False, inplace=True)  # 降序排列
counts_['ratio'] = counts_.iloc[:, 0] / counts_.iloc[:, 0].sum()
print(counts_)

 输出:

         num     ratio
type                  
101   411665  0.491570
199   201426  0.240523
107   182900  0.218401
301    18430  0.022007
102    17357  0.020726
106     3957  0.004725
103     1715  0.002048

 

 

 4.2 网页107类型中的内部统计

import pandas as pd
from sqlalchemy import create_engine

engine = create_engine('mysql+pymysql://root:011228@localhost:3306/test?charset=utf8')
sql = pd.read_sql('all_gzdata', engine.connect(), chunksize=10000)


# 因为只有107001一类,但是可以继续细分成三类:知识内容页、知识列表页、知识首页
def count107(i):  # 自定义统计函数
    j = i[['fullURL']][i['fullURLId'].str.contains('107')].copy()  # 找出类别包含107的网址
    j['type'] = None  # 添加空列
    j['type'][j['fullURL'].str.contains('info/.+?/')] = '知识首页'
    j['type'][j['fullURL'].str.contains('info/.+?/.+?')] = '知识列表页'
    j['type'][j['fullURL'].str.contains('/\d+?_*\d+?\.html')] = '知识内容页'
    return j['type'].value_counts()


counts2 = [count107(i) for i in sql]  # 逐块统计
counts2 = pd.concat(counts2).groupby(level=0).sum()  # 合并统计结果
# 计算各个部分的占比
res107 = pd.DataFrame(counts2)
# res107.reset_index(inplace=True)
res107.index.name = '107类型'
res107.rename(columns={'type': 'num'}, inplace=True)
res107['比例'] = res107['num'] / res107['num'].sum()
res107.reset_index(inplace=True)
print(res107)

输出:

   107类型     num        比例
0  知识内容页  164243  0.897993
1  知识列表页    9656  0.052794
2   知识首页    9001  0.049213

4.3 统计带"?"问号网址类型统计

import pandas as pd
from sqlalchemy import create_engine

print('4.3 统计带"?"问号网址类型统计')
engine = create_engine('mysql+pymysql://root:011228@localhost:3306/test?charset=utf8')
sql = pd.read_sql('all_gzdata', engine.connect(), chunksize=10000)


def countquestion(i):  # 自定义统计函数
    j = i[['fullURLId']][i['fullURL'].str.contains('\?')].copy()  # 找出类别包含107的网址
    return j


counts3 = [countquestion(i)['fullURLId'].value_counts() for i in sql]
counts3 = pd.concat(counts3).groupby(level=0).sum()
# 求各个类型的占比并保存数据
df1 = pd.DataFrame(counts3)
df1['perc'] = df1['fullURLId'] / df1['fullURLId'].sum() * 100
df1.sort_values(by='fullURLId', ascending=False, inplace=True)
print(df1.round(4))

输出:

4.3 统计带"?"问号网址类型统计
         fullURLId     perc
1999001      64718  98.8182
301001         356   0.5436
107001         346   0.5283
101003          47   0.0718
102002          25   0.0382

 

4.4 统计199类型中的具体类型占比

import pandas as pd
from sqlalchemy import create_engine

print('4.4 统计199类型中的具体类型占比')
engine = create_engine('mysql+pymysql://root:011228@localhost:3306/test?charset=utf8')
sql = pd.read_sql('all_gzdata', engine.connect(), chunksize=10000)


def page199(i):  # 自定义统计函数
    j = i[['fullURL', 'pageTitle']][(i['fullURLId'].str.contains('199')) &
                                    (i['fullURL'].str.contains('\?'))]
    j['pageTitle'].fillna('空', inplace=True)
    j['type'] = '其他'  # 添加空列
    j['type'][j['pageTitle'].str.contains('法律快车-律师助手')] = '法律快车-律师助手'
    j['type'][j['pageTitle'].str.contains('咨询发布成功')] = '咨询发布成功'
    j['type'][j['pageTitle'].str.contains('免费发布法律咨询')] = '免费发布法律咨询'
    j['type'][j['pageTitle'].str.contains('法律快搜')] = '快搜'
    j['type'][j['pageTitle'].str.contains('法律快车法律经验')] = '法律快车法律经验'
    j['type'][j['pageTitle'].str.contains('法律快车法律咨询')] = '法律快车法律咨询'
    j['type'][(j['pageTitle'].str.contains('_法律快车')) |
              (j['pageTitle'].str.contains('-法律快车'))] = '法律快车'
    j['type'][j['pageTitle'].str.contains('空')] = '空'

    return j


counts4 = [page199(i) for i in sql]  # 逐块统计
counts4 = pd.concat(counts4)
d1 = counts4['type'].value_counts()
d2 = counts4[counts4['type'] == '其他']
# 求各个部分的占比并保存数据
df1_ = pd.DataFrame(d1)
df1_['perc'] = df1_['type'] / df1_['type'].sum() * 100
df1_.sort_values(by='type', ascending=False, inplace=True)
print(df1_)

输出:

4.4 统计199类型中的具体类型占比
            type       perc
法律快车-律师助手  49894  77.094471
法律快车法律咨询    6421   9.921506
咨询发布成功      5220   8.065762
快搜          1943   3.002256
法律快车         818   1.263945
其他           359   0.554714
法律快车法律经验      59   0.091165
空              4   0.006181

 

4.5 统计瞎逛用户中各个类型占比

import pandas as pd
from sqlalchemy import create_engine

print("4.5 统计瞎逛用户中各个类型占比")
engine = create_engine('mysql+pymysql://root:011228@localhost:3306/test?charset=utf8')
sql = pd.read_sql('all_gzdata', engine.connect(), chunksize=10000)


def xiaguang(i):  # 自定义统计函数
    j = i.loc[(i['fullURL'].str.contains('\.html')) == False,
    ['fullURL', 'fullURLId', 'pageTitle']]
    return j


counts5 = [xiaguang(i) for i in sql]
counts5 = pd.concat(counts5)

xg1 = counts5['fullURLId'].value_counts()
# 求各个部分的占比
xg_ = pd.DataFrame(xg1)
xg_.reset_index(inplace=True)
xg_.columns = ['index', 'num']
xg_['perc'] = xg_['num'] / xg_['num'].sum() * 100
xg_.sort_values(by='num', ascending=False, inplace=True)

xg_['type'] = xg_['index'].str.extract('(\d{3})')  # 提取前三个数字作为类别id

xgs_ = xg_[['type', 'num']].groupby('type').sum()  # 按类别合并
xgs_.sort_values(by='num', ascending=False, inplace=True)  # 降序排列
xgs_['percentage'] = xgs_['num'] / xgs_['num'].sum() * 100

print(xgs_.round(4))

输出:

4.5 统计瞎逛用户中各个类型占比
         num  percentage
type                    
199   117124     71.2307
107    17843     10.8515
102    17357     10.5559
101     7130      4.3362
106     3957      2.4065
301     1018      0.6191

 

4.6 统计用户浏览网页次数的情况 

import pandas as pd
from sqlalchemy import create_engine

engine = create_engine('mysql+pymysql://root:011228@localhost:3306/test?charset=utf8')
sql = pd.read_sql('all_gzdata', engine.connect(), chunksize=10000)

counts1 = [i['realIP'].value_counts() for i in sql]  # 分块统计各个IP的出现次数
counts1 = pd.concat(counts1).groupby(level=0).sum()  # 合并统计结果,level=0表示按照index分组
print(counts1)

counts1_ = pd.DataFrame(counts1)
counts1['realIP'] = counts1.index.tolist()

counts1_[1] = 1  # 添加1列全为1
hit_count = counts1_.groupby('realIP').sum()  # 统计各个“不同点击次数”分别出现的次数
# 也可以使用counts1_['realIP'].value_counts()功能
hit_count.columns = ['用户数']
hit_count.index.name = '点击次数'
# 统计1~7次、7次以上的用户人数
hit_count.sort_index(inplace=True)
hit_count_7 = hit_count.iloc[:7, :]
time = hit_count.iloc[7:, 0].sum()  # 统计点击次数7次以上的用户数
hit_count_7 = hit_count_7.append([{'用户数': time}], ignore_index=True)
hit_count_7.index = ['1', '2', '3', '4', '5', '6', '7', '7次以上']
hit_count_7['用户比例'] = hit_count_7['用户数'] / hit_count_7['用户数'].sum()
print(hit_count_7)

 输出:

82033         2
95502         1
103182        1
116010        2
136206        1
             ..
4294809358    2
4294811150    1
4294852154    3
4294865422    2
4294917690    1
Name: realIP, Length: 230149, dtype: int64
         用户数      用户比例
1     132119  0.574059
2      44175  0.191941
3      17573  0.076355
4      10156  0.044128
5       5952  0.025862
6       4132  0.017954
7       2632  0.011436
7次以上   13410  0.058267

 

 

4.7 分析浏览次数为一次的用户的行为

import pandas as pd
from sqlalchemy import create_engine

engine = create_engine('mysql+pymysql://root:011228@localhost:3306/test?charset=utf8')
sql = pd.read_sql('all_gzdata', engine.connect(), chunksize=10000)

counts1 = [i['realIP'].value_counts() for i in sql]  # 分块统计各个IP的出现次数
counts1 = pd.concat(counts1).groupby(level=0).sum()  # 合并统计结果,level=0表示按照index分组
print(counts1)

counts1_ = pd.DataFrame(counts1)
counts1['realIP'] = counts1.index.tolist()

counts1_[1] = 1  # 添加1列全为1
hit_count = counts1_.groupby('realIP').sum()  # 统计各个“不同点击次数”分别出现的次数
# 也可以使用counts1_['realIP'].value_counts()功能
hit_count.columns = ['用户数']
hit_count.index.name = '点击次数'
# 统计1~7次、7次以上的用户人数
hit_count.sort_index(inplace=True)
hit_count_7 = hit_count.iloc[:7, :]
time = hit_count.iloc[7:, 0].sum()  # 统计点击次数7次以上的用户数
hit_count_7 = hit_count_7.append([{'用户数': time}], ignore_index=True)
hit_count_7.index = ['1', '2', '3', '4', '5', '6', '7', '7次以上']
hit_count_7['用户比例'] = hit_count_7['用户数'] / hit_count_7['用户数'].sum()
print(hit_count_7)

# 分析浏览一次的用户行为

engine = create_engine('mysql+pymysql://root:011228@localhost:3306/test?charset=utf8')
all_gzdata = pd.read_sql_table('all_gzdata', con=engine.connect())  # 读取all_gzdata数据

# 对realIP进行统计
# 提取浏览1次网页的数据
real_count = pd.DataFrame(all_gzdata.groupby("realIP")["realIP"].count())
real_count.columns = ["count"]
real_count["realIP"] = real_count.index.tolist()
user_one = real_count[(real_count["count"] == 1)]  # 提取只登录一次的用户
user_one = user_one.set_index("realIP")
real_one = pd.merge(user_one, all_gzdata, left_index=True, right_on="realIP")

# 统计浏览一次的网页类型
URL_count = pd.DataFrame(real_one.groupby("fullURLId")["fullURLId"].count())
URL_count.columns = ["count"]
URL_count.sort_values(by='count', ascending=False, inplace=True)  # 降序排列
# 统计排名前4和其他的网页类型
URL_count_4 = URL_count.iloc[:4, :]
time = hit_count.iloc[4:, 0].sum()  # 统计其他的
URLindex = URL_count_4.index.values
URL_count_4 = URL_count_4.append([{'count': time}], ignore_index=True)
URL_count_4.index = [URLindex[0], URLindex[1], URLindex[2], URLindex[3],
                     '其他']
URL_count_4['比例'] = URL_count_4['count'] / URL_count_4['count'].sum()
print(URL_count_4)

# 在浏览1次的前提下, 得到的网页被浏览的总次数
fullURL_count = pd.DataFrame(real_one.groupby("fullURL")["fullURL"].count())
fullURL_count.columns = ["count"]
fullURL_count["fullURL"] = fullURL_count.index.tolist()
fullURL_count.sort_values(by='count', ascending=False, inplace=True)  # 降序排列
print(fullURL_count)

输出:

          count        比例
101003   102560  0.649011
107001    19443  0.123037
1999001    9381  0.059364
301001      515  0.003259
其他        26126  0.165328
                                                    count                                            fullURL
fullURL                                                                                                     
http://www.lawtime.cn/info/shuifa/slb/201211197...   1013  http://www.lawtime.cn/info/shuifa/slb/20121119...
http://www.lawtime.cn/info/hunyin/lhlawlhxy/201...    501  http://www.lawtime.cn/info/hunyin/lhlawlhxy/20...
http://www.lawtime.cn/ask/question_925675.html        423     http://www.lawtime.cn/ask/question_925675.html
http://www.lawtime.cn/info/shuifa/slb/201211197...    367  http://www.lawtime.cn/info/shuifa/slb/20121119...
http://www.lawtime.cn/ask/exp/13655.html              301           http://www.lawtime.cn/ask/exp/13655.html
...                                                   ...                                                ...
http://www.lawtime.cn/ask/question_4230028.html         1    http://www.lawtime.cn/ask/question_4230028.html
http://www.lawtime.cn/ask/question_422994.html          1     http://www.lawtime.cn/ask/question_422994.html
http://www.lawtime.cn/ask/question_4229836.html         1    http://www.lawtime.cn/ask/question_4229836.html
http://www.lawtime.cn/ask/question_4229778.html         1    http://www.lawtime.cn/ask/question_4229778.html
http://zm10.sm.cn/?src=http%3A%2F%2Fwww.lawtime...      1  http://zm10.sm.cn/?src=http%3A%2F%2Fwww.lawtim...

[88035 rows x 2 columns]

 


五、构建智能推荐模型

基于物品的协同过滤算法(ItemCF)是一种在推荐系统中广泛使用的技术。该技术通过分析用户或者事物之间的相似性,来预测用户可能感兴趣的内容并将此内容推荐给用户。该算法的核心思想就是:给用户推荐那些和他们之前喜欢的物品相似的物品。主要可分为两步:

(1) 计算物品之间的相似度,建立相似度矩阵。

(2) 根据物品的相似度和用户的历史行为给用户生成推荐列表。

5.1 数据清洗

1、删除不符合规则的网页

import re
from random import sample

import pandas as pd
import pymysql as pm

# 读取数据
con = pm.connect(host='localhost', user='root', password='011228', database='test', charset='utf8')
data = pd.read_sql('select * from all_gzdata', con=con)
con.close()  # 关闭连接
# 取出107类型数据
index107 = [re.search('107', str(i)) != None for i in data.loc[:, 'fullURLId']]
data_107 = data.loc[index107, :]

# 在107类型中筛选出婚姻类数据
index = [re.search('hunyin', str(i)) != None for i in data_107.loc[:, 'fullURL']]
data_hunyin = data_107.loc[index, :]

# 提取所需字段(realIP、fullURL)
info = data_hunyin.loc[:, ['realIP', 'fullURL']]

# 去除网址中“?”及其后面内容
da = [re.sub('\?.*', '', str(i)) for i in info.loc[:, 'fullURL']]
info.loc[:, 'fullURL'] = da  # 将info中‘fullURL’那列换成da
# 去除无html网址
index = [re.search('\.html', str(i)) != None for i in info.loc[:, 'fullURL']]
index.count(True)  # True 或者 1 , False 或者 0
info1 = info.loc[index, :]
print(info1.head())

2、还原翻页地址 

# 找出翻页和非翻页网址
index = [re.search('/\d+_\d+\.html', i) != None for i in info1.loc[:, 'fullURL']]
index1 = [i == False for i in index]
info1_1 = info1.loc[index, :]  # 带翻页网址
info1_2 = info1.loc[index1, :]  # 无翻页网址
# 将翻页网址还原
da = [re.sub('_\d+\.html', '.html', str(i)) for i in info1_1.loc[:, 'fullURL']]
info1_1.loc[:, 'fullURL'] = da
# 翻页与非翻页网址合并
frames = [info1_1, info1_2]
info2 = pd.concat(frames)
# 或者
info2 = pd.concat([info1_1, info1_2], axis=0)  # 默认为0,即行合并
# 去重(realIP和fullURL两列相同)
info3 = info2.drop_duplicates()
# 将IP转换成字符型数据
info3.iloc[:, 0] = [str(index) for index in info3.iloc[:, 0]]
info3.iloc[:, 1] = [str(index) for index in info3.iloc[:, 1]]
print(len(info3))

3、筛去浏览次数不满两次的用户 

# 筛选满足一定浏览次数的IP
IP_count = info3['realIP'].value_counts()
# 找出IP集合
IP = list(IP_count.index)
count = list(IP_count.values)
# 统计每个IP的浏览次数,并存放进IP_count数据框中,第一列为IP,第二列为浏览次数
IP_count = pd.DataFrame({'IP': IP, 'count': count})
# 3.3筛选出浏览网址在n次以上的IP集合
n = 2
index = IP_count.loc[:, 'count'] > n
IP_index = IP_count.loc[index, 'IP']
print(IP_index.head())

5.2 划分IP集合为训练集和测试集

# 划分IP集合为训练集和测试集
index_tr = sample(range(0, len(IP_index)), int(len(IP_index) * 0.8))  # 或者np.random.sample
index_te = [i for i in range(0, len(IP_index)) if i not in index_tr]
IP_tr = IP_index[index_tr]
IP_te = IP_index[index_te]
# 将对应数据集划分为训练集和测试集
index_tr = [i in list(IP_tr) for i in info3.loc[:, 'realIP']]
index_te = [i in list(IP_te) for i in info3.loc[:, 'realIP']]
data_tr = info3.loc[index_tr, :]
data_te = info3.loc[index_te, :]
print(len(data_tr))
IP_tr = data_tr.iloc[:, 0]  # 训练集IP
url_tr = data_tr.iloc[:, 1]  # 训练集网址
IP_tr = list(set(IP_tr))  # 去重处理
url_tr = list(set(url_tr))  # 去重处理
print(len(url_tr))

5.3 模型构建


# 利用训练集数据构建模型
UI_matrix_tr = pd.DataFrame(0, index=IP_tr, columns=url_tr)
# 求用户-物品矩阵
for i in data_tr.index:
    print("********************1-" + str(i) + "-*********************")
    UI_matrix_tr.loc[data_tr.loc[i, 'realIP'], data_tr.loc[i, 'fullURL']] = 1
sum(UI_matrix_tr.sum(axis=1))

# 求物品相似度矩阵(因计算量较大,需要耗费的时间较久)
Item_matrix_tr = pd.DataFrame(0, index=url_tr, columns=url_tr)
print(len(Item_matrix_tr))
for i in Item_matrix_tr.index:
    print("*********************2-" + str(i) + "-********************")
    for j in Item_matrix_tr.index:
        print("******************3-" + str(j) + "-***********************")
        a = sum(UI_matrix_tr.loc[:, [i, j]].sum(axis=1) == 2)
        b = sum(UI_matrix_tr.loc[:, [i, j]].sum(axis=1) != 0)
        Item_matrix_tr.loc[i, j] = a / b

# 将物品相似度矩阵对角线处理为零
for i in Item_matrix_tr.index:
    print("*****************4-" + str(i) + "-************************")
    Item_matrix_tr.loc[i, i] = 0

# 利用测试集数据对模型评价
IP_te = data_te.iloc[:, 0]
url_te = data_te.iloc[:, 1]
IP_te = list(set(IP_te))
url_te = list(set(url_te))

# 测试集数据用户物品矩阵
UI_matrix_te = pd.DataFrame(0, index=IP_te, columns=url_te)
for i in data_te.index:
    print("*******************5-" + str(i) + "-**********************")
    UI_matrix_te.loc[data_te.loc[i, 'realIP'], data_te.loc[i, 'fullURL']] = 1

# 对测试集IP进行推荐
Res = pd.DataFrame('NaN', index=data_te.index,
                   columns=['IP', '已浏览网址', '推荐网址', 'T/F'])
Res.loc[:, 'IP'] = list(data_te.iloc[:, 0])
Res.loc[:, '已浏览网址'] = list(data_te.iloc[:, 1])

# 开始推荐
for i in Res.index:
    print("******************6-" + str(i) + "-***********************")
    if Res.loc[i, '已浏览网址'] in list(Item_matrix_tr.index):
        Res.loc[i, '推荐网址'] = Item_matrix_tr.loc[Res.loc[i, '已浏览网址'],
                                 :].argmax()
        if Res.loc[i, '推荐网址'] in url_te:
            Res.loc[i, 'T/F'] = UI_matrix_te.loc[Res.loc[i, 'IP'],
            Res.loc[i, '推荐网址']] == 1
        else:
            Res.loc[i, 'T/F'] = False

# 保存推荐结果
Res.to_csv('/tmp/Res.csv', index=False, encoding='utf8')

5.4 模型评价

import pandas as pd

# 读取保存的推荐结果
Res = pd.read_csv('../tmp/Res.csv', keep_default_na=False, encoding='utf8')

# 计算推荐准确率
Pre = round(sum(Res.loc[:, 'T/F'] == 'True') / (len(Res.index) - sum(Res.loc[:, 'T/F'] == 'NaN')), 3)
print("准确率:")
print(Pre)

# 计算推荐召回率
Rec = round(sum(Res.loc[:, 'T/F'] == 'True') / (sum(Res.loc[:, 'T/F'] == 'True') + sum(Res.loc[:, 'T/F'] == 'NaN')), 3)
print("召回率:")
print(Rec)

# 计算F1指标
F1 = round(2 * Pre * Rec / (Pre + Rec), 3)
print("F1指标:")
print(F1)

输出:

准确率:
0.128
召回率:
0.186
F1指标:
0.152

总结

        本次实验实现了基于 Python 语言的协同过滤推荐算法,并通过连接 MySQL 数据库、读取数据、数据清洗、矩阵计算等一系列操作,构建了基于物品的协同过滤算法,并利用测试集对模型进行了评价。


参考链接

https://blog.csdn.net/ichen820/article/details/121398385
https://blog.csdn.net/luckilyhuihuio/article/details/108018120


源码链接

链接: https://pan.baidu.com/s/1pGdwc2RAxz7nIlwr2CEYKg?pwd=dwzd 提取码: dwzd
 

  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值