因为在进行百度热搜语句的爬取时,它刚开始只能打开8条热搜,需要点击打开后面的12条记录,也就是这个网页是动态的。使用普通爬虫爬不到数据,所以需要用的selenium进行web自动化,模拟人的点击,打开网页。
模拟过程:首先在网页开发者工具中找到所匹配的内容元素的位置(找到标签)->找到之后可以右键copy xpath路径,可以在开发者工具中crtl + f4 搜索框验证。
xpath:XML Path Language,即 XML 路径语言,它是一门在 XML 文档中查找信息的语言。
自动化获取数据时要打开网站。
解决:利用无头模式隐藏浏览器。在后台运行浏览器。
headless:浏览器的无界面状态,可以在不打开浏览器GUI的情况下,使用浏览器支持的性能。 可以加快UI自动化测试的执行时间,对于UI自动化测试,少了真实浏览器加载css,js以及渲染页面的工作。无头测试要比真实浏览器快的多。
建立了三张表
问题:
数据冗余:爬取数据时,有时候网站数据没有更新,爬下来的数据和上一次爬取的一样,如果这时候插入数据库就会发生数据冗余。
解决方法:在更新数据库之前,对比当前的最大时间戳,若一样就不进行更新。
插入格式不匹配:爬取下来的json格式转dict格式后再插入数据库时,可能会有格式不匹配的情况。如时间
解决方法:转成数据库定义的格式。time模块
requests.get(url) 面对最基本的反爬,利用代理人模式 user-agent,伪装模拟访问,添加headers信息。
还有就是放慢爬取速度,selenium中暂停一段时间在点击。
使用render_template模板开发,
模板中用html(超文本标记)语言构建web页面。
用css定义网页的样式。
使用ajax局部刷新页面。在页面不刷新的情况下,向服务器发送请求,达到页面和后台的异步交互。
在jquery框架下编写ajax代码。post方式发送json格式字符串。
ajax技术将数据从后台实时的发送到前端。
echarts:数据可视化工具,其实就是模板图表库。
词云实现的关键词提取,主要是使用很火的中方分词器jieba
出现问题:
解决方法:
问题:不同的网站更新数据的频率不一样,如果每次调用爬虫都把所有的模块全实现一次的话,效率比较低
解决方法:利用system模块的sys.argv函数获取外界参数信息,用crontab定时进行调用不同模块的update。
背景——需求——重难点——结果
你只要答出来:项目是用来解决什么的,用到了什么技术,遇到了什么问题,是怎么解决的,项目完成之后产生了什么样的影响。围绕着这四个点就可以了。
做过一个网络爬虫可视化项目,是基于python + flask + mysql 的疫情管理系统。因为在20年疫情刚爆发的时候,人心惶惶嘛,我爸我妈还有家人们总是关心这个疫情的数据和情况,但是我观察他们获得这个情况的方式方法有点不太正确,家里因为长期没人,电视也没缴费,新闻看不了,他们就只能拿手机看消息,而看消息 = 看抖音,这样看来的消息呢,一是不全,二是不直观,三是效率低。加上那段时间在家上网课正好专业实习课的时候讲到了python,爬虫,我就想着做一个统计疫情数据的小网站给他们看。
需求分析:要将全国的数据,直观清楚的表达出来,形象化!
项目包括:数据爬取,数据持久化(存入数据库),构建网页后台、前端制作,以及数据可视化,(最后部署到服务器)。五个流程
爬取数据包括:请求数据和解析数据两部分。
数据持久化包括:建立数据库,表,通过pymysql模块与数据库连接交互,创建游标,执行操作,提交事务,关闭连接操作。将数据存储到数据库,并后续查询数据。建立了三张表。
构建网页后台包括:利用flask构建web、使用render_template模板开发、用css定义网页的样式、用echarts数据可视化工具添加修改所要用到的趋势图,中国地图,柱状图以及热搜图,
数据可视化包括:使用ajax局部刷新网页,将后台的数据传送的前端展示(中间包括了数据格式的转变)。在jquery框架下编写ajax。
部署定时爬虫调度:获取crontab定时调度。
背景——需求——重难点——结果
你只要答出来:项目是用来解决什么的,用到了什么技术,遇到了什么问题,是怎么解决的,项目完成之后产生了什么样的影响。围绕着这四个点就可以了。
python爬虫
python与mysql数据库交互
使用flask构建web项目
基于echarts数据可视化展示
在linux上部署web项目及爬虫
爬虫:就是给网站发起请求,并从响应中提取需要的数据的自动化程序。
发起请求,获取响应
通过http库,对目标站点进行请求。
服务器返回的内容一般为:html、二进制文件(视频、音频)、文档、json字符串等
解析内容
寻找需要的信息,利用正则表达式或者其他库提取信息
保存数据
使用urllib发送请求
res = request.urlopen() //获取响应对象
res.info()//响应头
res.getcode()//状态码 2xx 3xx 4xx
res.geturl()//返回响应地址
html = res.read() #接收网页源码
html = html.decode("utf-8") # 解码
# 面对最基本的反爬,利用代理人模式 user-agent,伪装模拟访问,添加headers信息
url = “网址”
header = { “User-agent”:xxx} #在response中,伪装浏览器
req = request.Request(url,headers = header)
res = request.urlopen(req) #获取响应
爬虫被封禁。爬虫会给服务器带来很大的负载,因此很多服务器会限制爬虫,甚至禁用爬虫。大家都知道要构造合理的http访问头,如user-agent域的值。但是,这里还有许多其它的避免被封禁的需要注意的问题,如放慢爬虫访问速度,使得爬虫访问路径和用户访问路径一致,采取动态ip地址等等
使用request发送请求
安装:pip install request
requests.get(url)
**beautifulsoup4 **解析内容
寻找标签元素!!!
href,taget属性;attr获取质点
url = "xxx"
res = request.get(url)
html = res.text
soup = BeautifulSoup(html)
soup.find("h2 #这是一个标签").text
a = soup.find("a")
url_new = "xxx" + a.attrs["href"] #将href拼接在网址后,得到下一级网址
res = requests.get(url_new)
res.encoding = "utf-8"
BeautifulSoup(res.text) #得到下一级网址内容数据
re解析内容
re是python自带的正则表达式模块,(了解正则表达式)
import re
text = context.text #已获取信息文本
patten = "新增确诊病例(\d+)例" # \d+正则表达式
res = re.search(patten.text)
res.groups()
res.group(0)#匹配到字符串本身
res.group(1)#第一个参数
.* #匹配之间的任意字符
.*? #非贪婪模式
requests.get 得到的是json字符串,利用json模块中的loads 可以转成字典
字典中有关键值key
import requests
import json
url = "xxx"
res = requests.get()
d = json.loads(res.text) # 将json格式转成字典dict格式
print(d["data"]) # 获取data key值
import time #时间模块
time.strptime #
time.strftime #改变时间格式,不然插入数据库会报错
history[ds] = {"confirm": confirm,"confirm_now":confirm_now, "suspect": suspect, "heal": heal, "dead": dead}
history[ds].update({"confirm_add": confirm_add, "suspect_add": suspect_add, "heal_add": heal_add, "dead_add": dead} #update是一个函数自动更新历史数据
数据存储
建立数据库
datetime(0) DEFAULT NULL COMMENT
varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT //建表格式弄清楚
使用pymysql 模块与数据库交互
建立连接
创建游标
游标:数据库重要概念;有时候,我们执行一条查询语句的时候,往往会得到N条返回结果,执行sql语句取出这些返回结果的接口(起始点),就是游标。沿着这个游标,我们可以一次取出一行记录。
执行操作
关闭连接
conn = pymysql.connect(host="127.0.0.1",
user="root",
password="mima@123",
db="cov",
charset="utf8")
# 创建游标
cursor = conn.cursor()# 执行完毕返回的结果集默认以元组显示
sql = "insert into history values(%s,%s)" #需要%s占位
cursor.execute(sql) #执行sql语句
cursor.commit() #提交事务
cursor.fetchall() #获得结果
return conn, cursor
def close_conn(conn, cursor):
cursor.close()
conn.close()
游标函数弄懂 连接数据库
traceback模块 追踪异常
动态渲染:通过js控制数据生成,普通爬虫请求数据获取不到数据。
# 返回body,body是前端中获取的最后部分,body中无内容
selenium: 一个用于web应用程序测试的工具,直接运行在浏览器中,就像真正的用户在操作一样
需要pip
需要浏览器(谷歌)
需要下载对应版本浏览器驱动
创建浏览器对象
浏览器.get()
浏览器.find()
from selenium webdriver import Chrome, ChromeOpthions
browser = Chrome() # 括号里添加驱动路径
browser.get(url) #url就是上面的, 会运行安装的浏览器驱动
print(browser.page_source) #打开浏览器之后就会获取数据内容
browser.find_element_by_xpath("xpath路径")#寻找元素
#xpath路径寻找 :定位元素-》右键copy xpath路径-》可以crtl+f 验证
# 找不到就只能分析网页源码敲
c = browser.find_elements_by_xpath("xpath路径")#print之后就可以获得
for i in c:
print(i,text) # 每条信息打印
#下面模拟人的点击
but = browser.find_element_by_css_selector("与寻找xpath一样的方法")
but.click()#点击展开
time.sleep(1) #等待一秒 面对反爬
#找到热搜标签
c = browser.find_elements_by_xpath("xpath路径")#print之后就可以获得
for i in c: #获取标签内容
print(i,text) # 每条信息打印
browser.close()
#上面获得数据时都需要打开浏览器 ,可以使用无头模式隐藏浏览器
from selenium webdriver import Chrome, ChromeOpthions
a = ChromeOpthions()
a.add_argument("--headless") #隐藏浏览器
browser = Chrome(chrome_options = a)
存储百度热搜数据到mysql中
新建一个表hotsearch有 id、dt、content三个字段 类型为int,datatime,varchar; id自增
#上面用selenium爬取到了数据,接着将数据存到数据库中
#接上面
#找到热搜标签
c = browser.find_elements_by_xpath("xpath路径")#print之后就可以在视图中看到
context = [i.text for i in c] #获取标签内容
return context #返回给函数
连接数据库
def update_hotsearch():
"""
将疫情热搜插入数据库
:return:
"""
cursor = None
conn = None
try:
context = get_baidu_hot()
print(f"{time.asctime()}开始更新热搜数据")
conn, cursor = get_conn()
sql = "insert into hotsearch(dt,content) values(%s,%s)"
ts = time.strftime("%Y-%m-%d %X")
for i in context:
cursor.execute(sql, (ts, i)) # 插入数据
conn.commit() # 提交事务保存数据
print(f"{time.asctime()}数据更新完毕")
except:
traceback.print_exc()
finally:
close_conn(conn, cursor)
from flask import Flask
app = Flask(_name_)
@app.route("/") #就是那个网址后的分隔符
def hello_world():
return "hello world"
@app.route("/abc")
def hello1():
return """
"""
if _name_ == '_main_':
app.run()
@app.route('/denglu')
def hello_world2():
name = request.values.get("name")
pwd = request.values.get("pwd")
return f'name={name},pwd={pwd}'
@app.route("/login")
def hello_world1():
id = request.values.get("id")
return f"""
"""
使用render_template返回模板页面。使用 html语法
from flask import render_template
p27 11分钟
原则:非常常用的中文分词关键词提取,重点放在改进。
原因 :做百度词云时需要对爬取下来的热搜文本做一个关键词提取
**part 1:简介 **
关键词提取是文本挖掘领域一个很重要的部分,通过对文本提取的关键词可以窥探整个文本的主题思想,进一步应用于文本的推荐或文本的搜索。
有监督算法:将关键字提取问题转换为判断每个候选关键词是否为关键词的二分类问题,它需要一个已经标注关键词的文档集合训练分类模型,非常费时费力
无监督算法:不要人工标注训练集,利用算法发现文本中比较重要的词作为关键词,进行关键词抽取。词重要性的衡量有多种方式:基于文本统计特征、基于词图模型和基于主题模型,TF-IDF、TextRank和LDA分别是这几种不同方式的代表。
part 2:TF-IDF关键词提取
TF-IDF是关键词提取最基本、最简单易懂的方法。
判断一个词再一篇文章中是否重要,一个容易想到的衡量指标就是词频,重要的词往往在文章中出现的频率也非常高;但另一方面,不是出现次数越多的词就一定重要,因为有些词在各种文章中都频繁出现,那它的重要性肯定不如哪些只在某篇文章中频繁出现的词重要性强。从统计学的角度,就是给予那些不常见的词以较大的权重,而减少常见词的权重。IDF(逆文档频率)就是这样一个权重,TF则指的是词频。
T F = 某 个 词 在 文 章 中 出 现 的 次 数 / 文 章 的 总 词 数 TF = 某个词在文章中出现的次数/文章的总词数 TF=某个词在文章中出现的次数/文章的总词数
I D F = l o g ( 语 料 库 的 文 档 总 数 / 包 含 该 词 的 文 档 数 + 1 ) IDF = log(语料库的文档总数/包含该词的文档数 + 1) IDF=log(语料库的文档总数/包含该词的文档数+1)
一个词IDF值的计算是根据语料库得出的,如果一个词在语料库中越常见,那么分母就越大,IDF就越小越接近0。分母之所以要加1,是为了避免分母为0(即所有文档都不包含该词)。
T F − I D F = T F ∗ I D F TF-IDF = TF * IDF TF−IDF=TF∗IDF
可以看出TF-IDF与一个词在文档中的出现次数成正比,与该词在整个语料库中出现次数成反比。一个词的TF-IDF值非常高,说明这个词比较少见,但是它在这篇文章中多次出现,那么这个词就非常可能是我们需要的关键词。
part3:改进
开发者可以指定自己定义的词典,以便包含jieba词库里没有的词。因为疫情以来出现了很多新的名词, 。虽然jieba有新词识别能力,但是自行添加新词可以保证更高的正确率。
通过用户自定义词典来增强歧义纠错能力。然后我们认为“中/ 君/ 意/ 是/ 你/ ”这几个连续的单字 中可能有词典中没有的新词,所以再用finalseg来切一遍“中君意是你 ”,finalseg是通过
模型来做的,简单来说就是给单字大上B,M,E,S四种标签以使得概率最大。
B: 开头
E:结尾
M:中间
S: 独立成词的单字
自定义词典用法: jieba.load_userdict(file_name) # file_name 为文件类对象或自定义词典的路径。jieba库中给的接口。
补充词意用法:
==user.dict===
君意 3
==test.py==
词典格式和 dict.txt
一样,一个词占一行;每一行分三部分:词语、词频(可省略)、词性(可省略),用空格隔开,顺序不可颠倒。file_name
若为路径或二进制方式打开的文件,则文件必须为 UTF-8 编码。
词频省略时使用自动计算的能保证分出该词的词频。
类内分散度问题。设计到文本分类稍微提一下,说自己暂时没有了解那么清楚。某些词会在某个类中都出现,而在其他类中涉及较少比如口罩、医院,会导致其 IDF值较低,从而导致TF-IDF也会较低,但是这些词又是我们想要的关键词。降低了精准性。
改进方法
T F I D F ′ = T F I D F ∗ W I TFIDF' = TFIDF * WI TFIDF′=TFIDF∗WI
WI为权重参数,定义如下
W I = 1 + l n [ ( e + n i ) / ( e + m i ) ] WI = 1 + ln[(e + ni)/(e + mi)] WI=1+ln[(e+ni)/(e+mi)]
其中e为趋于0的极小正数,ni为在当前类出现频率,mi为在其他类出现的频率。
在jieba库中这个方法用不了,因为其词语的idf值是经过IDF语料库得到的。IDF预料库就是jieba官方在大量文本的基础上,通过
I D F = l o g ( 语 料 库 的 文 档 总 数 / 包 含 该 词 的 文 档 数 + 1 ) IDF = log(语料库的文档总数/包含该词的文档数 + 1) IDF=log(语料库的文档总数/包含该词的文档数+1)
计算得到的一个idf字典,无法分类。
所以只能选择调整词典的方法。
调整词典
使用 add_word(word, freq=None, tag=None)`` 和 ``del_word(word)
可在程序中动态修改词典
使用suggest_freq可调节单个词语的词频
,使其可以或者不可以被分出来
长词优先:
腾讯科技有限公司 0.01 腾讯 0.1 科技 0.2 有限公司 0.1
四个词的概率如上,可以看到腾讯科技有限公司
的概率要比其他几个词低一个数量级。
但是P(腾讯) * P(科技) * P(有限公司) = 0.1 * 0.2 * 0.1 = 0.002 < 0.01
def extract_tags(self, sentence, topK=20, withWeight=False, allowPOS=(), withFlag=False):
# 传入了词性限制集合
if allowPOS:
allowPOS = frozenset(allowPOS)
# 调用词性标注接口
words = self.postokenizer.cut(sentence)
# 没有传入词性限制集合
else:
# 调用分词接口
words = self.tokenizer.cut(sentence)
freq = {}
for w in words:
if allowPOS:
if w.flag not in allowPOS:
continue
elif not withFlag:
w = w.word
wc = w.word if allowPOS and withFlag else w
# 判断词的长度是否小于2,或者词是否为停用词
if len(wc.strip()) < 2 or wc.lower() in self.stop_words:
continue
# 将其添加到词频词典中,次数加1
freq[w] = freq.get(w, 0.0) + 1.0
# 统计词频词典中的总次数
total = sum(freq.values())
for k in freq:
kw = k.word if allowPOS and withFlag else k
# 计算每个词的tf-idf值
freq[k] *= self.idf_freq.get(kw, self.median_idf) / total
# 根据tf-idf值进行排序
if withWeight:
tags = sorted(freq.items(), key=itemgetter(1), reverse=True)
else:
tags = sorted(freq, key=freq.__getitem__, reverse=True)
# 输出topK个词作为关键词
if topK:
return tags[:topK]
else:
return tags