泰迪杯数据挖掘挑战赛C题 通用论坛正文提取
来源:互联网 发布:淘宝网其他账户注册 编辑:程序博客网 时间:2024/06/05 11:21
参与第五届泰迪杯,侥幸获得二等奖,简单记录一下。
一、问题的背景
在当今的大数据时代里,伴随着互联网和移动互联网的高速发展,人们产生的数据总量呈现急剧增长的趋势,当前大约每六个月互联网中产生的数据总量就会翻一番。互联网产生的海量数据中蕴含着大量的信息,已成为政府和企业的一个重要数据来源,互联网数据处理也已成为一个有重大需求的热门行业。借助网络爬虫技术,我们能够快速从互联网中获取海量的公开网页数据,对这些数据进行分析和挖掘,从中提取出有价值的信息,能帮助并指导我们进行商业决策、舆论分析、社会调查、政策制定等工作。但是,大部分网页数据是以半结构化的数据格式呈现的,我们需要的信息在页面上往往淹没在大量的广告、图标、链接等“噪音”元素中。如何从网页中有效提取所需要的信息,一直是互联网数据处理行业关注的重点问题之一。
二、问题目标
对于任意 BBS 类型的网页,获取其 HTML 文本内容,设计一个智能提取该页面的主贴、所有回帖的算法。如下面的网页截图所示,提取主贴和回帖的区域,提取出相应数据字段(只需要提取文本, 图片、视频、音乐等媒体可以直接忽略),并按规定的数据格式(Json 格式)存储。
重要说明:
1. Json 数据字段说明:
post :主题帖
author: 用户名
title:标题
content:帖子内容
publish_date:帖子的发布日期,格式: yyyy-MM-dd
replys: 该页的回帖列表
每条回帖的主要字段同 post, 若回帖无 title 字段,可为空
2. 算法要求:
算法必须具有通用性,必须支持互联网的任意类型 BBS 网站,不得只针对附件所
给的样例网站、或特定类型的开源论坛(例如 discuz、phpwind)
二、数据样例
对于任意 BBS 类型的网页,获取其 HTML 文本内容,设计一个智能提取该页面的主贴、所有回帖的算法。如下面的网页截图所示,提取主贴和回帖
1. 样例输入数据格式:(每行一条论坛的内容页的 url)
http://bbs.tianya.cn/post-stocks-1841155-1.shtml (包含主贴的 url)
http://bbs.tianya.cn/post-stocks-1841155-3.shtml (不包含主贴的 url)
2. 样例输出数据格式(必须按如下格式提交结果):
每行数据有 {原始 url}\t {提取结果的 json 字符串}, 表示某一个 html 页面(url)提取出来的数据,示例数据格式:http://x.heshuicun.com/forum.php?mod=viewthread&tid=80{"post":{"content":"本帖最后由 临风有点冷 于 2012-7-27 08:26 编辑 从这条新闻中你得到了什么教训 在网上看个新闻,大概内容是:老公买了一只藏獒幼仔,没时间养,一直是老婆在养;一次老公老婆吵架,老公把老婆打了,结果藏獒冲出来果断把老公手咬断了!看完新闻我问老公:“从这条新闻中你得到了什么教训?” 本想听他说不能打老婆,没想到这货居然说:“ 游客,如果您要查看本帖隐藏内容请回复”","title":"从这条新闻中你得到了什么教训?","publish_date":"20120727"},"replys":[{"content":"的分数高如果认购二哥让他退给我","title":"从这条新闻中你得到了什么教训?
","publish_date":"20160904"},{"content":"1231231231321321","title":"从这条新闻中你得到了什么教训?","publish_date":"20161115"},{"content":"","title":"从这条新闻中你得到了什么教训?","publish_date":"20161208"}]}
三、解题
对于任意论坛来说,每个楼层之间的html代码结构必为相似的,所以第一步,通过枚举+结构对比的方式,提取出一个楼层的html代码。如下图:
可以看懂啊,每层楼都被一个id为post_xxxxx的div包含在内。
继续提取作者信息,作者信息我们使用了两种思路研究,一种是直接标签提取,作者一般会在有author这样的标签中,我们罗列了以下标签:
第二种思路算是有点奇淫巧技,对于大多数论坛内的楼层,作者一半位于最左上角,也就是第一个有实际内容的a标签:
def getAuthor(document): #可以精确分割bbs时提取作者 soup = BeautifulSoup(document,'html.parser') alist = soup.find_all('a') #print('author') for i in alist: if len(i.get_text())>0: # print(i.get_text()) return i.get_text() #print('me') return ''时间信息:使用正则表达式[2][0]\d{2}-\d+-\d+。
正文内容,使用标签提取以及基于文本密度模型的Web正文抽取方法,在提取时会遇到一些干扰因素,罗列并过滤:
关键代码放出来,没有认真整理,比较烂:
extractor.pyimport requests as req import re DBUG = 0 reBODY = r'<body.*?>([\s\S]*?)<\/body>' reCOMM = r'<!--.*?-->' reTRIM = r'<{0}.*?>([\s\S]*?)<\/{0}>' reTAG = r'<[\s\S]*?>|[ \t\r\f\v]' reIMG = re.compile(r'<img[\s\S]*?src=[\'|"]([\s\S]*?)[\'|"][\s\S]*?>') class Extractor(): def __init__(self, url = "", blockSize=5, timeout=5, image=False): self.url = url self.blockSize = blockSize self.timeout = timeout self.saveImage = image self.rawPage = "" self.ctexts = [] self.cblocks = [] def getRawPage(self): try: resp = req.get(self.url, timeout=self.timeout) except Exception as e: raise e if DBUG: print(resp.encoding) resp.encoding = resp.apparent_encoding return resp.status_code, resp.text #去除所有tag,包括样式、Js脚本内容等,但保留原有的换行符\n: def processTags(self): self.body = re.sub(reCOMM, "", self.body) self.body = re.sub(r'<img.*?>','',self.body) self.body = re.sub(reTRIM.format("script"), "" ,re.sub(reTRIM.format("style"), "", self.body)) # self.body = re.sub(r"[\n]+","\n", re.sub(reTAG, "", self.body)) self.body = re.sub(reTAG, "", self.body) #将网页内容按行分割,定义行块 blocki 为第 [i,i+blockSize] 行文本之和并给出行块长度基于行号的分布函数: def processBlocks(self): self.ctexts = self.body.split("\n") self.textLens = [len(text) for text in self.ctexts] self.cblocks = [0]*(len(self.ctexts) - self.blockSize - 1) lines = len(self.ctexts) for i in range(self.blockSize): self.cblocks = list(map(lambda x,y: x+y, self.textLens[i : lines-1-self.blockSize+i], self.cblocks)) print(self.cblocks) print(lines) maxTextLen = max(self.cblocks) if DBUG: print(maxTextLen) self.start = self.end = self.cblocks.index(maxTextLen) print(self.start,self.end,maxTextLen) while self.start > 0 and self.cblocks[self.start] > min(self.textLens): self.start -= 1 while self.end < lines - self.blockSize and self.cblocks[self.end] > min(self.textLens): self.end += 1 return "\n".join(self.ctexts[self.start:self.end]) #如果需要提取正文区域出现的图片,只需要在第一步去除tag时保留<img>标签的内容: def processImages(self): self.body = reIMG.sub(r'{{\1}}', self.body) #正文出现在最长的行块,截取两边至行块长度为 0 的范围: def getContext(self): code, self.rawPage = self.getRawPage() self.body = re.findall(reBODY, self.rawPage)[0] if DBUG: print(code, self.rawPage) if self.saveImage: self.processImages() self.processTags() return self.processBlocks() # print(len(self.body.strip("\n")))
separateAttribute.pyfrom bs4 import BeautifulSoupimport codecsimport re#document为html的Str#根据标签获得信息如果获得的信息为空则返回空字符串#path为文本路径#标签格式为: 标签名,属性,属性值 例如div,class,authidef del_content_blank(s): clean_str = re.sub(r'\r|\n| |\xa0|\\xa0|\u3000|\\u3000|\\u0020|\u0020', '', str(s)) return clean_str def getContent(document,path): soup = BeautifulSoup(document,'html.parser') list = [] results = [] for line in codecs.open(path,"rb","utf8"): a = line.replace("\n", "").split(",") list.append(a) for i in list: results = soup.select(i[0] + '[' + i[1] + '^=' + '"' + del_content_blank(i[2]) + '"]') if (len(results) != 0): break; if (len(results) != 0): soup = BeautifulSoup(str(results[0]), 'html.parser') return soup.text else: return ""def getAuthor(document): #可以精确分割bbs时提取作者 soup = BeautifulSoup(document,'html.parser') alist = soup.find_all('a') #print('author') for i in alist: if len(i.get_text())>0: # print(i.get_text()) return i.get_text() #print('me') return ''def getAuth(document): #print('auth') soup = BeautifulSoup(document,'html.parser') list = [] for line in codecs.open("author.txt","rb","utf8"): a = line.replace("\n", "").split(",") list.append(a) for i in list: results = soup.select(i[0] + '[' + i[1] + '^=' + '"' + del_content_blank(i[2]) + '"]') if (len(results) != 0): break; if (len(results) != 0): soup = BeautifulSoup(str(results[0]), 'html.parser') return soup.get_text() else: return ""#text为除去标签后的信息#得到时间def getTimeByRegular(text): #print(text) a=re.search("发表于\s*[2][0]\d{2}-\d+-\d+", text) #print(a.group()) if(a!=None): return a.group() else: a=re.search("发表于[2][0]\d{2}-\d+-\d+",text) if(a!=None): return a.group() else: a=re.findall("[2][0]\d{2}-\d+-\d+",text) #print(a) if(len(a)>0): return a[0] else: return ''#text为去掉标签后的信息(ps:不要去掉空格以及换行符)#过滤掉不需要的信息def filterinfo(text,author,time): result='' strs=text.split("\n") texts=[]; removelist=[] for str in strs: if(str!=''): texts.append(str) #print(texts) for str in texts: file=codecs.open("redundant.txt","rb","utf8") lines=file.readlines(); for line in lines: a=del_content_blank(line) conut=re.findall(a, str) if(len(conut)!=0): removelist.append(str) break; conut = re.findall(time,str) if(len(conut)!=0): removelist.append(str) break; conut = re.findall(author,str) if(len(conut)!=0): removelist.append(str) break; file.close() l = list(set(removelist)) for remove in l: texts.remove(remove) for tex in texts: result+=tex+"\n" return result
do.pyfrom urllib.request import urlopen ,Request from bs4 import BeautifulSoup from extractor import Extractorfrom separateAttribute import filterinfo,getTimeByRegular,getAuthor,getContent,getAuthimport urllibimport re import time import jsonimport socket from collections import OrderedDict socket.setdefaulttimeout(10)def del_content_blank(s): clean_str = re.sub(r'\r|\n| |\xa0|\\xa0|\u3000|\\u3000|\\u0020|\u0020|\t', '', str(s)) return clean_str def findmax(a): res = [] length = -1 for i in a: if(len(i)>length): length = len(i) res = i return resdef Tagtostring(a): s = str(a) s=re.sub(r'<\s*script[^>]*>[^<]*<\s*/\s*script\s*>','',s) #过滤script s=re.sub(r'<style.*?>.*?</style>','',s) #过滤css s=re.sub(r'<!--.*?-->','',s) #过滤注释 s=re.sub(r'<img.*?>','',s) return sdef judge(a,b): al=[] bl=[] for j in (a.descendants): al.append(j.name) for j in (b.descendants): bl.append(j.name) f = 0 for i in range(5): if al[i] == bl[i]: f+=1 if f>4: return True else: return Falsedef connect(url): try: head = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36'} req = Request(url=url,headers=head) res = urlopen(req) H = BeautifulSoup(res,"html.parser",from_encoding="GB2312") return H except (urllib.error.HTTPError, urllib.error.URLError, socket.timeout, ConnectionAbortedError,ConnectionResetError) as e: return {"replys": [], "post": {"content": "", "publish_data": "", "author": "", "title": ""}}def gettitle(H): if H.title is None: return 'title' else: title = str(H.title.get_text()) return titledef getinfo(H,url): dic = {} if type(H)==type(dic): return else: j = H.find('title') #type tag rule = ['link','meta','link','script','a','li','html','head','input','span','font','form','dt','p', 'i','ul','ol','dl','br','b','img','noscript','dd','body','title'] tlist = [] for i in H.descendants: if type(i) == type(j): if i.name in rule: continue tlist.append(i) nlist = [] for i in tlist: if len(list(i.descendants))>30 and len(re.findall(r'[2][0]\d{2}-\d+-\d+',Tagtostring(i)))>0: nlist.append(i) row = [] l = len(nlist) for i in range(l): t = [] t.append(i) j = i + 1 while j < l: if nlist[j].find_parent() == nlist[i].find_parent() and judge(nlist[i],nlist[j]): #判断父节点和子节点结构相似性 t.append(j) j+=1 row.append(t) res = findmax(row) authlist = [] timelist = [] conlist = [] if len(res)<2: #只有一层楼 采用文本密度提取 ext = Extractor(url=url,blockSize=6, image=False) content = ext.getContext() #content = Document(str(H)).summary() #content = re.sub(r'<.*?>','',content) #content = re.sub(r'</.*?>','',content) conlist.append(content) #print('content:',content) auth = getAuth(str(H)) authlist.append(auth) #print('auth:',auth) time = getTimeByRegular(str(H)) timelist.append(time) #print('time',time) else:#帖子较多时,通过分割楼层提取 for i in res: #print('====:',nlist[i]) #type tag s = Tagtostring(nlist[i]) author = getAuthor(s) authlist.append(author) #print('author:',author) time = getTimeByRegular(s) timelist.append(time) #print('time:',time) content =getContent(s,"content.txt") #print('content:',content) if content =='': content =filterinfo(BeautifulSoup(s,'html.parser').get_text(),author,time) #print(content) conlist.append(content) #print("********************\n") return authlist,timelist,conlistdef formattime(time): res = [] for i in time: i=re.search("[2][0]\d{2}-\d+-\d+",i) if i!=None: res.append(i.group()) return resdef do(url): H = connect(url) dic = {} if type(H) == type(dic): return {"replys": [], "post": {"content": "", "publish_data": "", "author": "", "title": ""}} auth,time,content = getinfo(H,url) time = formattime(time) #print('time',time) #print(auth,time,content) title = gettitle(H) res = {} length =len(auth) f ='{'+'\"post\":{\"content\":\"'+del_content_blank(content[0])+'\",\"author\":\"'+auth[0]+'\",\"title\":\"'+title+'\",\"publish_data\":\"'+time[0]+'\"'+ '},'; f+='\"replys\":[' for i in range(1,length): f+='{\"content\":\"'+del_content_blank(content[i])+'\",\"author\":\"'+auth[i]+'\",\"title\":\"'+'\",\"publish_data\":\"'+time[i]+'\"'+ '}' if i!=length-1: f+=',' f+=']}' print(f) #print(f) return fdo("http://bbs.lol.qq.com/forum.php?mod=viewthread&tid=3067734&extra=page%3D1")运行结果:
json解析一下:
- 泰迪杯数据挖掘挑战赛C题 通用论坛正文提取
- 【数据挖掘】通用论坛正文提取
- 通用论坛正文提取算法设计
- python通用论坛正文提取\python论坛评论提取\python论坛用户信息提取
- 基于DBSCAN聚类算法的通用论坛正文提取
- 基于DBSCAN聚类算法的通用论坛正文提取
- PHP 通用正文提取
- 数据挖掘笔记-聚类-DBSCAN-网页正文提取
- 正文提取
- 正文提取
- 数据挖掘之提取关键词
- 数据挖掘比赛通用步骤
- 数据挖掘期刊、论坛、博客、数据集
- 提取html正文
- nutch正文提取分析
- 网页正文提取
- 网页正文提取
- seo 提取 正文 算法
- [JAVA学习笔记-75]关于CAS
- 安全测试-SQL注入
- Error:Cause: failed to find target with hash string 'android-24' in...
- 程序员_你和高考隔了几年?
- 多个源文件共享一个全局变量
- 泰迪杯数据挖掘挑战赛C题 通用论坛正文提取
- 不差啊,Vultr纽约机房2.5美元月付VPS简单测评
- [JAVA学习笔记-76]volatile的原子性与可见性
- java设计模式之策略模式
- 清除浮动
- 实战gulp
- 卸载Oracle11g的步骤
- RESTful API 设计指南
- Hibernate映射文件中对应的mysql数据库表字段的类型