TF-IDF 实践
打算分以下几个部分进行
1. 用python写一个爬虫爬取网易新闻
2. 用分词工具对爬下来的文字进行处理, 形成语料库
3. 根据TF-IDF, 自动找出新闻的关键词
4. 根据TF-IDF, 实现相似新闻推荐
step 1a
今天一天都在弄python爬虫, 花了好大力气才写出一个勉强可用的版本
- # -*- coding: utf-8 -*
- import re, urllib, sys
- import pyodbc
- newsLink = set()##获取的所有新闻
- processLink = set()##正在处理的新闻
- newLink = set()##新读取的新闻
- viewedLink = set()##已经读取过的新闻
- ##打开输入的链接, 用正则表达式找出新页面中其他的链接, 并添加到全局set中
- def getNewsLink(link):
- ##print link
- if(link in viewedLink):
- return
- viewedLink.add(link)
- content = ""
- try:##这一步可能会抛出异常
- content = urllib.urlopen(link).read().decode('gbk').encode('utf-8')
- except:
- info=sys.exc_info()
- print info[0],":",info[1]
- print "caused by link : ", link
- m = re.findall(r"news\.163\.com/\d{2}/\d{4}/\d{2}/\w+\.html",content,re.M)##网易新闻链接格式为http://news.163.com/14/0621/12/9V8V9AL60001124J.html
- for i in m:
- url = "http://" + i
- newLink.add(url)
- newsLink.add(url)
- print "crawled %d page, get %d link"%(len(viewedLink), len(newsLink))
- ##将读取到的新闻ID存入数据库中
- def saveNewsIDtoDB():
- newsID = dict()
- for link in newsLink:
- ID = link[31:47]
- newsID[ID] = link##截取其中新闻ID
- conn = pyodbc.connect('DRIVER={SQL Server};SERVER=STEVEN-PC\\MSSQLSERVER_R2;DATABASE=TF-IDF;UID=sa;PWD=123456')
- cursor = conn.cursor()
- for (ID, url) in newsID.items():
- sql = "INSERT INTO News(NewsID, Url) VALUES ('%s','%s')"%(ID, url)
- try:
- cursor.execute(sql)
- except:
- info=sys.exc_info()
- print info[0],":",info[1]
- print "caused by sql : ", sql
- conn.commit()
- conn.close()
- print "total get %d news ID"%(len(newsID))
- ##读取指定数量的新闻
- def readNews(count):
- processLink = set()
- processLink.add("http://news.163.com/")
- while(len(newsLink) < count):
- for link in processLink:
- getNewsLink(link)
- processLink = newLink.copy()
- newLink.clear()
- readNews(10000)
- saveNewsIDtoDB()
实现了自动抓取指定数量的新闻并将其ID存入数据库
网易新闻没有公开其API, 但是新闻链接的格式都是固定的
如同http://news.163.com/14/0621/12/9V8V9AL60001124J.html, 14代表年份, 0621代表日期, 12不知道什么意思, 但是一定是两位数字, 后面的16位字符串就是新闻ID
跑了几十分钟, 抓了10360个新闻链接
step 1b
用BeautifulSoup解析链接, 得到新闻的标题, 正文, 和发布时间
跑了接近一个小时吧, 得到9714条新闻记录, 中间折损了接近一千条, 有的是新闻已经被删除了, 也有的是因为新闻正文格式不对, 抓了一堆JS代码进来, 存到数据库的时候就报错了
不过已经够了
解析代码如下
- # encoding: utf-8
- import re, urllib, sys
- import pyodbc, json
- import socket
- from bs4 import BeautifulSoup
- socket.setdefaulttimeout(10.0)
- def readNews():
- conn = pyodbc.connect('DRIVER={SQL Server};SERVER=STEVEN-PC\\MSSQLSERVER_R2;DATABASE=TF-IDF;UID=sa;PWD=123456')
- cursor = conn.cursor()
- sql = "SELECT * FROM News"
- cursor.execute(sql)
- rows = cursor.fetchall()
- updateCount = 0;
- for row in rows:#从数据库中读取链接
- print row.NewsID, row.Url
- content = ""
- ptime = ""
- title = ""
- body = ""
- newsID = row.NewsID.strip()
- try:##这一步可能会抛出异常
- content = urllib.urlopen(row.Url).read()#读取网页内容
- ptime = "" + row.Url[20:22] + "-" + row.Url[23:25] + "-" + row.Url[25:27]#新闻发布日期
- title, body = analyzeNews(content)#解析网页内容, 获取新闻标题与正文
- except:
- info=sys.exc_info()
- print info[0],":",info[1]
- print "caused by link : ", row.Url
- continue
- sql = "UPDATE News SET Title = '%s', Body = '%s',ptime = '%s' WHERE NewsID = '%s'"%(title, body, ptime, newsID)#生成sql语句
- try:##这一步可能会抛出异常
- cursor.execute(sql)
- except:
- info=sys.exc_info()
- print info[0],":",info[1]
- print "caused by sql : ", sql
- continue
- updateCount += 1
- if(updateCount % 100 == 0):
- conn.commit()
- print "已经更新了%s条数据!"%(updateCount)
- conn.commit()
- conn.close()
- print "数据处理完毕, 一共更新了%s条数据!"%(updateCount)
- def analyzeNews(content):
- soup = BeautifulSoup(content, from_encoding="gb18030")
- title = soup.title.get_text()[:-7]
- bodyHtml = soup.find(id = "endtext")
- if(bodyHtml == None):
- bodyHtml = soup.find(id = "text")
- if(bodyHtml == None):
- bodyHtml = soup.find(id = "endText")
- body = bodyHtml.get_text()
- body = re.sub("\n+", "\n", body)#去除连续的换行符
- print title
- return title, body
- readNews()
step 2
用结巴分词对新闻做分词并存入数据库中, 标题的权重设为正文的五倍
没想到数据库的效率这么高, 每秒钟居然能执行近万条插入语句
代码如下
- # -*- coding: utf-8 -*
- import re, urllib, sys
- import pyodbc
- import jieba
- stop = [line.strip().decode('utf-8') for line in open('chinese_stopword.txt').readlines() ]
- def readNewsContent():
- conn = pyodbc.connect('DRIVER={SQL Server};SERVER=STEVEN-PC\\MSSQLSERVER_R2;DATABASE=TF-IDF;UID=sa;PWD=123456')
- cursor = conn.cursor()
- sql = "SELECT * FROM News"
- cursor.execute(sql)
- rows = cursor.fetchall()
- word_dict = dict()#所有词的频数
- insert_count = 0;
- for row in rows:#从数据库中读取新闻
- content = row.Body
- title = row.Title
- newsID = row.NewsID.strip()
- seg_dict = sliceNews(title, content)#切词
- newsWordCount = 0
- for(word, count) in seg_dict.items():
- newsWordCount += count
- sql = "INSERT INTO ContentWord(Word, Count, NewsID) VALUES ('%s',%d, '%s')"%(word, count, newsID)#将每篇新闻的词频存入数据库中
- cursor.execute(sql)
- insert_count += 1
- if(insert_count % 10000 == 0):
- print "插入%d条新闻词频记录!"%(insert_count)
- if(word in word_dict):#维护word_dict
- word_dict[word] += 1
- else:
- word_dict[word] = 1
- sql = "UPDATE News SET WordCount = '%d' WHERE NewsID = '%s'"%(newsWordCount, newsID)
- cursor.execute(sql)
- conn.commit()
- print "一共插入%d条新闻词频记录!"%(insert_count)
- #将word_dict存入数据库中
- for(word, count) in word_dict.items():
- sql = "INSERT INTO TotalWord(Word, Count) VALUES ('%s',%d)"%(word, count)
- cursor.execute(sql)
- print "插入%d条总词频记录!"%(len(word_dict.items()))
- conn.commit()
- conn.close()
- #对输入文字切词, 并返回去除停用词后的词频
- def sliceNews(title, content):
- title_segs = list(jieba.cut(title))
- segs = list(jieba.cut(content))
- for i in range(5):#标题权重算正文权重的五倍
- segs += title_segs
- seg_set = set(segs)
- seg_dict = dict()
- for seg in seg_set:#去除停用词, 并得到这篇新闻里的词频
- if(seg not in stop and re.match(ur"[\u4e00-\u9fa5]+", seg)):#只匹配中文
- seg_dict[seg] = segs.count(seg)
- return seg_dict
- readNewsContent()
几分钟就跑完了, 一共插入1475330条新闻词频记录和135961条总词频记录
step 3
然后对分词结果做计算, 求其TF-IDF值, 得到每篇新闻的TF-IDF值最高的头20个词语, 作为关键词, 并保存到数据库中
代码如下
- # -*- coding: utf-8 -*
- import re, urllib, sys
- import pyodbc
- import math
- conn = pyodbc.connect('DRIVER={SQL Server};SERVER=STEVEN-PC\\MSSQLSERVER_R2;DATABASE=TF-IDF;UID=sa;PWD=123456')
- cursor = conn.cursor()
- newsCount = 0;
- totalWordDict = dict()
- def init():
- #读取所有新闻数
- sql = "SELECT COUNT(*) FROM News"
- cursor.execute(sql)
- row = cursor.fetchone()
- global newsCount
- newsCount = int(row[0])
- #读取总词频并构造字典
- sql = "SELECT * FROM TotalWord"
- cursor.execute(sql)
- rows = cursor.fetchall()
- for row in rows:
- totalWordDict[row.Word.strip()] = int(row.Count)
- def clean():
- conn.commit()
- conn.close()
- #计算所有新闻的关键词的tf-idf值
- def cacluTFIDF():
- sql = "SELECT * FROM NEWS"#遍历新闻
- cursor.execute(sql)
- rows = cursor.fetchall()
- insertCount = 0
- for row in rows:#对每一条新闻计算其关键词的TFIDF值
- newsID = row.NewsID.strip()
- keyWordList = calcuKeyWords(newsID)
- for keyWord in keyWordList:#将计算出的TFIDF值存入数据库中
- word = keyWord[0]
- value = keyWord[1]
- sql = "INSERT INTO TFIDF(Word, Value, NewsID) VALUES ('%s',%f, '%s')"%(word, value, newsID)
- cursor.execute(sql)
- insertCount += 1
- if(insertCount % 10000 == 0):
- print "插入%d条TFIDF记录!"%(insertCount)
- conn.commit()
- print "一共插入%d条TFIDF记录!"%(insertCount)
- #计算指定新闻的关键词
- def calcuKeyWords(newsID):
- newsID = newsID.strip()
- sql = "SELECT * FROM NEWS WHERE NewsID = '%s'"%(newsID)
- cursor.execute(sql)
- newsWordCount = cursor.fetchone().WordCount#新闻的总词数
- sql = "SELECT * FROM ContentWord WHERE NewsID = '%s'"%(newsID)
- cursor.execute(sql)
- rows = cursor.fetchall()
- tfidf_dict = dict()
- global newsCount
- #构建这篇新闻的tf-idf字典
- for row in rows:
- word = row.Word.strip()
- count = row.Count
- tf = float(count) / newsWordCount
- idf = math.log(float(newsCount) / (totalWordDict[word] + 1))
- tfidf = tf * idf
- tfidf_dict[word] = tfidf
- #取前20个关键词
- keyWordList = sorted(tfidf_dict.items(), key=lambda d: d[1])[-20:]
- return keyWordList
- init()
- cacluTFIDF()
- clean()
比方说对于 重庆东胜煤矿5名遇难者遗体全部找到 这条新闻
程序计算出来的关键词, 按权重从低到高排列分别为:
窜\年产\采空区\工人\冒落\东翼\煤约\矸\重庆市\南川\名\顶板\工作面\采煤\找到\遇难者\重庆\遗体\煤矿\东胜
step 4
然后就可以根据关键词来做自动推荐了
具体操作如下(引用自阮一峰的博客)
(1)使用TF-IDF算法,找出两篇文章的关键词;
(2)每篇文章各取出若干个关键词(比如20个),合并成一个集合,计算每篇文章对于这个集合中的词的词频(为了避免文章长度的差异,可以使用相对词频);
(3)生成两篇文章各自的词频向量;
(4)计算两个向量的余弦相似度,值越大就表示越相似。
代码如下
- # -*- coding: utf-8 -*
- import re, urllib, sys
- import pyodbc
- import math
- conn = pyodbc.connect('DRIVER={SQL Server};SERVER=STEVEN-PC\\MSSQLSERVER_R2;DATABASE=TF-IDF;UID=sa;PWD=123456')
- cursor = conn.cursor()
- def clean():
- conn.commit()
- conn.close()
- #计算两条新闻的相似度, 返回结果为这两条新闻的关键词之间的余弦距离
- def similar(newsID1, newsID2):
- newsID1 = newsID1.strip()
- newsID2 = newsID2.strip()
- #取得待对比的两个新闻的关键词集合
- sql = "SELECT * FROM TFIDF WHERE NewsID = '%s' OR NewsID = '%s'"%(newsID1, newsID2)
- cursor.execute(sql)
- rows = cursor.fetchall()
- wordSet = set()
- for row in rows:
- wordSet.add(row.Word)
- #计算两条新闻中关键词的各自出现次数, 用向量表示
- vector1 = []
- vector2 = []
- for word in wordSet:
- sql = "SELECT * FROM ContentWord WHERE NewsID = '%s' AND Word = '%s'"%(newsID1, word)
- cursor.execute(sql)
- rows = cursor.fetchall()
- if len(rows) == 0:
- vector1.append(0)
- else:
- vector1.append(int(rows[0].Count))
- sql = "SELECT * FROM ContentWord WHERE NewsID = '%s' AND Word = '%s'"%(newsID2, word)
- cursor.execute(sql)
- rows = cursor.fetchall()
- if len(rows) == 0:
- vector2.append(0)
- else:
- vector2.append(int(rows[0].Count))
- return calcuCosDistance(vector1, vector2)
- #计算两个输入向量之间的余弦距离
- def calcuCosDistance(a, b):
- if len(a) != len(b):
- return None
- part_up = 0.0
- a_sq = 0.0
- b_sq = 0.0
- for a1, b1 in zip(a,b):
- part_up += a1*b1
- a_sq += a1**2
- b_sq += b1**2
- part_down = math.sqrt(a_sq*b_sq)
- if part_down == 0.0:
- return None
- else:
- return part_up / part_down
- #输入一个新闻ID, 输出与其最相似的头几条新闻
- def recommand(newsID):
- limit = 5
- result = dict()
- sql = "SELECT * FROM NEWS"#遍历新闻
- cursor.execute(sql)
- rows = cursor.fetchall()
- newsID = newsID.strip()
- calcuCount = 0
- for row in rows:
- calcuCount += 1
- if calcuCount % 200 == 0:
- print "已经计算了%d对新闻的相似度"%(calcuCount)
- if row.NewsID.strip() != newsID:#去掉本身
- distance = similar(newsID, row.NewsID)#计算两个新闻的相似度
- if len(result) < limit:
- result[distance] = row.NewsID
- else:
- minDis = min(result.keys())
- if(minDis < distance):
- del result[minDis]
- result[distance] = row.NewsID
- print "输入的新闻编号为%s"%(newsID)
- sql = "SELECT * FROM NEWS WHERE NewsID = '%s'"%(newsID)
- cursor.execute(sql)
- row = cursor.fetchone()
- print "输入的新闻链接为: %s"%(row.Url.encode('utf-8'))
- print "输入的新闻标题为: %s"%(row.Title.decode('gb2312').encode('utf-8'))
- print "--------------------------------------"
- for sim, newsID in result.items():
- sql = "SELECT * FROM NEWS WHERE NewsID = '%s'"%(newsID)
- cursor.execute(sql)
- row = cursor.fetchone()
- print "推荐新闻的相似度为: %f"%(sim)
- print "推荐新闻的编号为: %s"%(row.NewsID.encode('utf-8'))
- print "推荐新闻的链接为: %s"%(row.Url.encode('utf-8'))
- print "推荐新闻的标题为: %s"%(row.Title.decode('gb2312').encode('utf-8'))
- print ""
- #print similar("2IK789GB0001121M", "2IKJ8KRJ0001121M")
- recommand("A4AVPKLA00014JB5")
- clean()
- 输入的新闻编号为: A4AVPKLA00014JB5
- 输入的新闻链接为: http://news.163.com/14/0823/10/A4AVPKLA00014JB5.html
- 输入的新闻标题为: 重庆东胜煤矿5名遇难者遗体全部找到
- --------------------------------------
- 推荐新闻的相似度为: 0.346214
- 推荐新闻的编号为: A4BHA5OO0001124J
- 推荐新闻的链接为: http://news.163.com/14/0823/15/A4BHA5OO0001124J.html
- 推荐新闻的标题为: 安徽淮南煤矿爆炸事故救援再次发现遇难者遗体
- 推荐新闻的相似度为: 0.356118
- 推荐新闻的编号为: 8H0Q439K00011229
- 推荐新闻的链接为: http://news.163.com/12/1123/16/8H0Q439K00011229.html
- 推荐新闻的标题为: 安徽淮北首富被曝用500万元买通矿难遇难者家属
- 推荐新闻的相似度为: 0.320387
- 推荐新闻的编号为: A3MBB7CF00014JB6
- 推荐新闻的链接为: http://news.163.com/14/0815/10/A3MBB7CF00014JB6.html
- 推荐新闻的标题为: 黑龙江鸡西煤矿透水事故9人升井 仍有16名矿工被困
- 推荐新闻的相似度为: 0.324280
- 推荐新闻的编号为: 5Q92I93D000120GU
- 推荐新闻的链接为: http://news.163.com/09/1211/16/5Q92I93D000120GU.html
- 推荐新闻的标题为: 土耳其煤矿发生瓦斯爆炸 19名矿工全部遇难
- 推荐新闻的相似度为: 0.361950
- 推荐新闻的编号为: 6D7J4VLR00014AED
- 推荐新闻的链接为: http://news.163.com/10/0804/05/6D7J4VLR00014AED.html
- 推荐新闻的标题为: 贵州一煤矿发生煤与瓦斯突出事故
推荐内容的关联性很好
不过, 由于推荐操作需要对数据库进行遍历, 时间复杂度非常高, 对单个新闻做关联推荐耗时大约在10分钟左右, 实际使用肯定是无法接受的
但是, 毕竟只是个很粗糙的测试, 我个人还是非常满意的
我的感受是: 算法挺神奇, 在上面的代码中, 完全不需要知道具体的新闻内容, 程序就能自动做出相当准确的判断, 非常方便而且有趣
中间还是有很多可以优化的地方
比如爬取新闻的时候可以删除部分无用信息(来源, 记者姓名之类)
根据词语出现的位置, 对TF-IDF值进行修正, 比方说第一段和每一段的第一句话的TF-IDF值应当更高一点
对新闻进行粗略分类, 在对一篇新闻做关联推荐的时候, 不需要遍历整个新闻库
参考资料: 阮一峰的博客 http://www.ruanyifeng.com/blog/2013/03/tf-idf.html
TF-IDF 实践的更多相关文章
- TF/IDF(term frequency/inverse document frequency)
TF/IDF(term frequency/inverse document frequency) 的概念被公认为信息检索中最重要的发明. 一. TF/IDF描述单个term与特定document的相 ...
- 基于TF/IDF的聚类算法原理
一.TF/IDF描述单个term与特定document的相关性TF(Term Frequency): 表示一个term与某个document的相关性. 公式为这个term在document中出 ...
- 使用solr的函数查询,并获取tf*idf值
1. 使用函数df(field,keyword) 和idf(field,keyword). http://118.85.207.11:11100/solr/mobile/select?q={!func ...
- TF/IDF计算方法
FROM:http://blog.csdn.net/pennyliang/article/details/1231028 我们已经谈过了如何自动下载网页.如何建立索引.如何衡量网页的质量(Page R ...
- tf–idf算法解释及其python代码实现(下)
tf–idf算法python代码实现 这是我写的一个tf-idf的简单实现的代码,我们知道tfidf=tf*idf,所以可以分别计算tf和idf值在相乘,首先我们创建一个简单的语料库,作为例子,只有四 ...
- tf–idf算法解释及其python代码实现(上)
tf–idf算法解释 tf–idf, 是term frequency–inverse document frequency的缩写,它通常用来衡量一个词对在一个语料库中对它所在的文档有多重要,常用在信息 ...
- 文本分类学习(三) 特征权重(TF/IDF)和特征提取
上一篇中,主要说的就是词袋模型.回顾一下,在进行文本分类之前,我们需要把待分类文本先用词袋模型进行文本表示.首先是将训练集中的所有单词经过去停用词之后组合成一个词袋,或者叫做字典,实际上一个维度很大的 ...
- 信息检索中的TF/IDF概念与算法的解释
https://blog.csdn.net/class_brick/article/details/79135909 概念 TF-IDF(term frequency–inverse document ...
- Elasticsearch学习之相关度评分TF&IDF
relevance score算法,简单来说,就是计算出,一个索引中的文本,与搜索文本,他们之间的关联匹配程度 Elasticsearch使用的是 term frequency/inverse doc ...
- tf idf公式及sklearn中TfidfVectorizer
在文本挖掘预处理之向量化与Hash Trick中我们讲到在文本挖掘的预处理中,向量化之后一般都伴随着TF-IDF的处理,那么什么是TF-IDF,为什么一般我们要加这一步预处理呢?这里就对TF-IDF的 ...
随机推荐
- 02 Django框架基础(APP的创建访问)
一.创建项目 1.命令:django-admin startproject sitename 2.IDLE环境:本质上都是执行上述命令 常用命令: python manage.py runserver ...
- LeetCode939
问题:最小面积矩形 给定在 xy 平面上的一组点,确定由这些点组成的矩形的最小面积,其中矩形的边平行于 x 轴和 y 轴. 如果没有任何矩形,就返回 0. 示例 1: 输入:[[1,1],[1,3], ...
- verilog 1995 VS 2001 part1模块声明的扩展
1.模块声明的扩展 (1)端口声明(input/output/inout)同数据类型声明(reg /wire)放在同一语句中. (2)ANSI C风格的端口声明可以用于module/task/func ...
- 稀疏表(ST / Sparse Table)
RMQ问题: 给定一个序列,每次询问一个区间最小值 / 最大值. 没有修改. //拿区间最大值来举例. memset(ans, -INF, sizeof(ans)); for (int i = 1; ...
- debian卸载vmware
原因: 由于vagrant默认支持virtualbox,而要支持vmware需要一个商用付费的插件.所以卸载vmware,使用virtualbox 具体操作: $ sudo vmware-instal ...
- 2049: [Sdoi2008]Cave 洞穴勘测(LCT)
2049: [Sdoi2008]Cave 洞穴勘测 Time Limit: 10 Sec Memory Limit: 259 MBSubmit: 9962 Solved: 4824[Submit] ...
- Hive学习笔记(四)-- hive的桶表
桶表抽样查询 查看hdfs上对应的文件内容 一个两个桶,第一个桶和第三个桶的数据 task = 4 4 / 2 = 2,一共是两个桶 第1个桶,第1+2个桶
- luogu3178 [HAOI2015]树上操作
裸题 #include <iostream> #include <cstdio> using namespace std; typedef long long ll; int ...
- canvas 动画库 CreateJs 之 EaselJS(下篇)
本文来自网易云社区 作者:田亚楠 继承 对应原文:Inheritance 我们可以继承已有的「显示对象」,创建新的自定义类.实现方法有很多种,下面介绍其中之一. 举例:实现一个继承于 Containe ...
- socketcluster 客户端请求
<html> <head> <title>test</title> <script src="https://cdn.bootcss.c ...