打算分以下几个部分进行

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()
输入刚才的新闻ID, 得到的结果为

 输入的新闻编号为:   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 实践的更多相关文章

  1. TF/IDF(term frequency/inverse document frequency)

    TF/IDF(term frequency/inverse document frequency) 的概念被公认为信息检索中最重要的发明. 一. TF/IDF描述单个term与特定document的相 ...

  2. 基于TF/IDF的聚类算法原理

        一.TF/IDF描述单个term与特定document的相关性TF(Term Frequency): 表示一个term与某个document的相关性. 公式为这个term在document中出 ...

  3. 使用solr的函数查询,并获取tf*idf值

    1. 使用函数df(field,keyword) 和idf(field,keyword). http://118.85.207.11:11100/solr/mobile/select?q={!func ...

  4. TF/IDF计算方法

    FROM:http://blog.csdn.net/pennyliang/article/details/1231028 我们已经谈过了如何自动下载网页.如何建立索引.如何衡量网页的质量(Page R ...

  5. tf–idf算法解释及其python代码实现(下)

    tf–idf算法python代码实现 这是我写的一个tf-idf的简单实现的代码,我们知道tfidf=tf*idf,所以可以分别计算tf和idf值在相乘,首先我们创建一个简单的语料库,作为例子,只有四 ...

  6. tf–idf算法解释及其python代码实现(上)

    tf–idf算法解释 tf–idf, 是term frequency–inverse document frequency的缩写,它通常用来衡量一个词对在一个语料库中对它所在的文档有多重要,常用在信息 ...

  7. 文本分类学习(三) 特征权重(TF/IDF)和特征提取

    上一篇中,主要说的就是词袋模型.回顾一下,在进行文本分类之前,我们需要把待分类文本先用词袋模型进行文本表示.首先是将训练集中的所有单词经过去停用词之后组合成一个词袋,或者叫做字典,实际上一个维度很大的 ...

  8. 信息检索中的TF/IDF概念与算法的解释

    https://blog.csdn.net/class_brick/article/details/79135909 概念 TF-IDF(term frequency–inverse document ...

  9. Elasticsearch学习之相关度评分TF&IDF

    relevance score算法,简单来说,就是计算出,一个索引中的文本,与搜索文本,他们之间的关联匹配程度 Elasticsearch使用的是 term frequency/inverse doc ...

  10. tf idf公式及sklearn中TfidfVectorizer

    在文本挖掘预处理之向量化与Hash Trick中我们讲到在文本挖掘的预处理中,向量化之后一般都伴随着TF-IDF的处理,那么什么是TF-IDF,为什么一般我们要加这一步预处理呢?这里就对TF-IDF的 ...

随机推荐

  1. Linux基础学习-crond系统计划任务

    系统计划任务 大部分系统管理工作都是通过定期自动执行某个脚本来完成的,那么如何定期执行某个脚本,从而实现运维的自动化,这就要借助Linux的cron功能了. 计划任务分为一次性计划任务和周期性计划任务 ...

  2. Linux菜鸟起飞之路【六】权限管理(二)

    一.权限信息详解   ls -l 文件 //查看文件权限写法1 ll 文件 //查看文件权限写法2 ls -dl 目录 //查看目录权限写法1 ll -d 目录 //查看目录权限写法2 文件权限格式: ...

  3. destoon 屏蔽会员组,让个人,游客不显示

    include/post.fun.php  文件的group_select函数增加 排除参数 except function group_select($name = 'groupid', $titl ...

  4. 数据库储存session信息代码

    今天给大家上一段代码,数据库存储session信息,你只需要将下面这段代码放到session文件中,然后再session_start()的地方引入sessiong文件就行啦,当然你就不用再写sessi ...

  5. matplotlib学习记录 二

    # 绘制10点到12点的每一分钟气温变化折线图 import random from matplotlib import pyplot as plt # 让matplotlib能够显示中文 plt.r ...

  6. Linux学习-透过 systemctl 管理服务

    透过 systemctl 管理单一服务 (service unit) 的启动/开机启动与观察状态 一般来说,服务的启动有两个阶段,一 个是『开机的时候设定要不要启动这个服务』, 以及『你现在要不要启动 ...

  7. JAVA 基础--开发环境IDEA 搭建

    1.下载IDEA  (500M+) 2.激活. 在网站http://idea.lanyus.com/中获取注册码,填入Activation code中: 然后点击Activate即可. 3.创建工程前 ...

  8. adb pull 文件夹的时候注意

    传说之美 分享快乐 记录生活 学习探索 博客园 首页 新随笔 联系 管理 订阅 随笔- 75  文章- 0  评论- 19  Android 用adb pull或push 拷贝手机文件到到电脑上,拷贝 ...

  9. 如果奇迹有颜色,那么一定是暴力or模拟比较6

    模拟就是一个思想,给你一个东西,没有很好的算法去解决,只需要计算机去暴力,优雅的暴力就可以叫算法了 主要还是考大家的代码能力,这次题目应该不需要任何前置技能. 1001 Time Limit(Comm ...

  10. API经济时代的思考(转载目的:为之后写API-first模式的生命周期治理做准备)

    原文地址:API经济时代的思考    感觉这篇博客还不错,个人赞同其大部分的内容,借鉴参考一下,懒得自己写了(关键是不一定能轻松写得更好,嘿嘿,偷懒啦) 接下来会写关于API经济的概念下,如何进行AP ...