目标:对常见手机应用商店评论进行爬取,对第三方数据网上的评论数据进行爬取
1. 爬虫
爬虫过程:获取评论数据→写入数据库
1.1 静态网页爬虫:requests库
以某应用商店的outlook页面为例:
- 在打开的应用页面右键点击
检查
,然后刷新一下页面,选中network
,再选中Fetch/XHR
,发现有一个网页评论数据url(下图3)
- 复制该url在新的页面打开,发现就是我们需要的评论数据,只是被编码过(没关系)
- 继续分析该url:http://app.so.com/message/index?page=1&requestType=ajax&_t=1661237476289&name=Outlook+Android_com.microsoft.office.outlook,经过测试发现:
&_t=1661237476289
是时间戳,去掉后不影响返回结果;
page=1
是我们请求的评论页数,可以自行更换(后面爬虫就是对page进行遍历)
&name=Outlook+Android_com.microsoft.office.outlook
是应用的名字。 - 然后我们就可以使用
requests.get(url)
方法,获取该url返回的评论数据,利用json.loads(res.text)
方法读取返回的文字内容。import requests import json headers = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36' } res = requests.get('http://app.so.com/message/index?page=1&requestType=ajax&_t=1661237476289&name=Outlook+Android_com.microsoft.office.outlook', headers=headers) # 加载评论内容 json_str = json.loads(res.text)
完整代码如下
import requests
import json
import pymysql
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
}
# 要爬取的应用url
appname = {
'Outlook':'Outlook+Android_com.microsoft.office.outlook',
'Office':'Office+Mobile+for+Office+365+Android_com.microsoft.office.officehub',
}
# 连接数据库
db = pymysql.connect(
host="127.0.0.1", # 本机地址
port=3306, # 默认端口
user='root', #在这里输入用户名
password='root', #在这里输入密码
database='comments', # 数据库名
charset='utf8mb4'
)
cursor = db.cursor() #创建游标对象,用于数据库操作
for k,v in appname.items():# 遍历应用列表
for i in range(100): # 爬取前100页
# 获取请求网页内容
res = requests.get('http://app.so.com/message/index?page='+str(i)+'&requestType=ajax&name='+str(v), headers=headers)
# 如果获取页面为[],表明无内容
if res.content == b'\n\n\n[]':
break
# 加载评论内容
json_str = json.loads(res.text)
for item in json_str:
# 通过msgid判断是否在数据库已存在此条数据
querysql = "select msgid from database_comments_360 where msgid="+pymysql.converters.escape_string(str(item.get('msgid')))
if(cursor.execute(querysql)==0):# execute(querysql)返回的是操作影响的行数
sql = 'insert into database_comments_360(date,user,content,score,version,name, msgid) values(%s,%s,%s,%s,%s,%s,%s);'
date = pymysql.converters.escape_string(item.get('create_time')) # 获取数据
user = pymysql.converters.escape_string(item.get('username'))
content = pymysql.converters.escape_string(item.get('content'))
score = pymysql.converters.escape_string(str(item.get('score')))
version = pymysql.converters.escape_string(str(item.get('version_name')))
msgid = pymysql.converters.escape_string(str(item.get('msgid')))
name = pymysql.converters.escape_string(str(k))
data = [date,user,content,score,version,name,msgid]
cursor.execute(sql,data) # 插入数据
db.commit() # 提交命令 (如果不提交,不会进行数据库操作)
print('爬取'+str(k)+'应用评论'+str(i)+'页')
cursor.close()
db.close() #关闭数据库连接
1.2 动态网页爬虫:selenium库
selenium是一个web测试工具,可以模拟浏览器操作,如打开网页,点击元素等。
在使用selenium前需要安装浏览器版本对应的chromedriver,解压后把chromedriver.exe的路径加入用户变量Path中
- 对某第三方网站的评论页面url进行分析:
某发烧厂商店edge评论:https://app.diandian.com/app/6njqcmu6xj0rip4/android-review?system=4&market=3&summer=
某蓝厂商店edge评论:https://app.diandian.com/app/rn6vtpukqjzvhw4/android-review?system=4&market=4&summer=
发现只有两部分不一样,分别是应用名字和market,而&summer=
经过测试可以删掉,不影响返回结果。这样我们就获得了需要的url。 - 在页面点击右键,选中
检查
,发现该网站使用js动态加载数据,无法像静态网页一样通过url获取评论数据。 - 点击
element
,用鼠标选中评论数据数据所在的位置,也就是左侧的表格,可以看到右侧有评论数据内容,我们就可以通过css选择器来定位到该数据:
- 同样也可以定位到用户评分和用户名等内容,如下图所示:
- 我们可以使用selenium中
By.CSS_SELECTOR
来定位到我们需要的元素,css样式通过下图中copy selector来获取。
- 分析完成后,使用selenium中的webdriver模拟对浏览器进行操作
from selenium import webdriver # 模拟浏览器操作 from selenium.webdriver.common.by import By # 后面用By里面的css选择器定位网页元素 driver = webdriver.Chrome()# 初始化一个浏览器对象 driver.get("https://app.diandian.com/app/4q3uzuzpe5x8es7/ios-review?system=4") # 打开网页 table=driver.find_element(By.CSS_SELECTOR,'#commentContent > div.loading-wrap > div > div > div > table')#使用css定位网页表格位置 #获取表格包含的行,并将行数赋值 time.sleep(2) table_rows=table.find_elements(By.CSS_SELECTOR,'#commentContent > div.loading-wrap > div > div > div > table > tbody > tr')# table包含行数的集合,包含标题 print('table_rows',table_rows) vrows=len(table_rows)#将总行数赋给变量vrows,后面对每一行进行遍历 print('vrows',vrows) # 对表格进行按行遍历 for table_num in range(1, vrows): time.sleep(2) # 爬取评论内容 content = driver.find_element(by=By.CSS_SELECTOR, value="#commentContent > div.loading-wrap > div > div > div > table > tbody > tr:nth-child("+str(table_num)+") > td:nth-child(2) > div > div > div.dd-word-wrap > div > p > span").text # 爬取评论分数 time.sleep(2) score = driver.find_element(by=By.CSS_SELECTOR, value="#commentContent > div.loading-wrap > div > div > div > table > tbody > tr:nth-child("+str(table_num)+") > td:nth-child(1) > div.dd-table-cell > div > div").get_attribute("aria-valuenow")
完整代码如下:
# diandian.py 文件
from selenium import webdriver # 模拟浏览器操作
from selenium.webdriver.common.by import By # 后面用By里面的css选择器定位网页元素
import pymysql # 对MySQL数据库进行连接、插入数据等操作
def myscrapy(urls,market,appname): # 这里封装了函数,需要传入三个参数,分别是应用的url,market哪个应用市场,appname是数据库表名,指示要写入哪个数据库表
driver = webdriver.Chrome()# 初始化一个浏览器对象
# 连接数据库
db = pymysql.connect(
host="127.0.0.1",
port=3306,
user='root', #在这里输入用户名
password='root', #在这里输入密码
database='comments', #数据库名
charset='utf8mb4'
)
cursor = db.cursor() #创建游标对象
# 删除表中数据 (非必需模块,每次爬取都先删除数据库中已有部分,重新爬取)
sql='truncate table database_comments_'+str(appname)+';'
cursor.execute(sql)
db.commit()
for k,v in urls.items():# 遍历要爬取的应用urls
driver.get("https://app.diandian.com/app/"+str(v)+"/android-review?market="+str(market)+"&summer=") # 打开网页
# 爬前10页
for i in range(10):
print("正在爬取第",i,"页")
# 点击下一页
if (i!=0): # 在第一页之后的页面,爬取之前需要点击当前页面的‘下一页’按钮,刷新后再进行爬取
try:#try处理部分应用只有一页评论,没有nextbutton按钮
next_button = driver.find_element(by=By.CSS_SELECTOR, value="#commentContent > div.loading-wrap > div > div > div > div > div > button.btn-next")
disable = next_button.get_attribute("disabled")
# 如果下一页按钮可用再点击,否则跳出此次循环
if(disable!='true'):
next_button.click()
print("next page")
else:
print("没有点击下一页")
break
except:
break
# 开始爬表格数据 sleep是为了防止刷新过快,页面元素失效无法获取
time.sleep(2)
table=driver.find_element(By.CSS_SELECTOR,'#commentContent > div.loading-wrap > div > div > div > table')#使用css定位网页表格位置
#获取表格包含的行,并将行数赋值
time.sleep(2)
table_rows=table.find_elements(By.CSS_SELECTOR,'#commentContent > div.loading-wrap > div > div > div > table > tbody > tr')# table包含行数的集合,包含标题
print('table_rows',table_rows)
vrows=len(table_rows)#将总行数赋给变量vrows,后面进行循环
print('vrows',vrows)
for table_num in range(1, vrows):
time.sleep(2)
# 爬取评论内容
try:
content = driver.find_element(by=By.CSS_SELECTOR, value="#commentContent > div.loading-wrap > div > div > div > table > tbody > tr:nth-child("+str(table_num)+") > td:nth-child(2) > div > div > div.dd-word-wrap > div > p > span").text
print('content:',content)
except:
continue
# 爬取评论分数
time.sleep(2)
score = driver.find_element(by=By.CSS_SELECTOR, value="#commentContent > div.loading-wrap > div > div > div > table > tbody > tr:nth-child("+str(table_num)+") > td:nth-child(1) > div.dd-table-cell > div > div").get_attribute("aria-valuenow")
print('score:',score)
# 爬取评论时间
time.sleep(2)
date = driver.find_element(by=By.CSS_SELECTOR, value="#commentContent > div.loading-wrap > div > div > div > table > tbody > tr:nth-child("+str(table_num)+") > td:nth-child(3) > div > div").text
print('date:',date)
# 爬取评论应用版本
try:
version = driver.find_element(by=By.CSS_SELECTOR, value="#commentContent > div.loading-wrap > div > div > div > table > tbody > tr:nth-child("+str(table_num)+") > td:nth-child(2) > div > div > div.comment-info.dd-flex.dd-flex-warp > span:nth-child(6)").text
print('version:',version)
except:
version = " "
print("not found version!")
time.sleep(2)
try:
user = driver.find_element(by=By.CSS_SELECTOR, value="#commentContent > div.loading-wrap > div > div > div > table > tbody > tr:nth-child("+str(table_num)+") > td:nth-child(2) > div > div > div.comment-info.dd-flex.dd-flex-warp > div > a").text
print('user:',user)
except:
user = "anoy"
print("not found user!")
time.sleep(2)
name = driver.find_element(by=By.CSS_SELECTOR, value=" #appinfo-content > div.container > div:nth-child(2) > div.content-side > div.container-head > div.dd-flex-1.dd-flex.dd-flex-column.dd-flex-space.dd-overflow-hidden > div.logo-wrap > div.max-width-80 > div.app-name > h1").text
print('name:',name)
data = [date,user,content,score,version,name]
print(data)
sql = 'insert into database_comments_'+str(appname) +'(date,user,content,score,version,name) values(%s,%s,%s,%s,%s,%s);'
cursor.execute(sql,data) # 插入数据
db.commit() # 提交sql语句
cursor.close()
db.close() # 关闭数据库连接
driver.quit() # 退出浏览器
下面是调用部分,以某第三方数据网的vivo商店评论为例:
# all.py文件
import diandian
# 爬取第三方数据网蓝厂商店的下面几个应用评论数据
vivo_urls={
'Outlook':'jn79t9ueolpbqy3',
'Office':'3403tku16vp5t14',
'Edge':'rn6vtpukqjzvhw4',
}
diandian.myscrapy(vivo_urls,4,'vivo')
2. 定时任务 sched库
参考链接:Python事件调度器定时任务sched
-
利用内置模块
sched
实现定时任务
sched 模块实现了一个通用事件调度器,在调度器类中使用一个延迟函数,等待特定的时间,执行任务。但该方法会阻塞线程,直到所有被调度的任务都执行完成。 -
class sched.scheduler(timefunc, delayfunc)
这个类定义了调度事件的通用接口,需要外部传入两个参数,
timefunc
:无参数,返回时间戳的函数。常用的有time
模块里面的 time。
delayfunc
:需要一个参数,与timefunc
的输出兼容,常用的有time
模块的 sleep。''' 初始化scheduler类对象 time.time 返回时间戳 time.sleep 在定时未到达之前阻塞 用time模块的这两个函数来实例化scheduler对象 ''' schedule = sched.scheduler(time.time, time.sleep)
-
scheduler
对象主要方法:enter(delay, priority, action, argument)
,延迟 delay 个时间单位安排一个事件。cancel(event)
:从队列中删除事件。如果事件不是当前队列中的事件,则该方法将抛出一个 ValueError。run()
:运行所有预定的事件。这个函数将等待(使用delayfunc()
函数),然后执行事件,直到不再有预定的事件。
代码示例:
import sched # 定时任务模块
import time # 时间模块
import datetime # 日期时间模块
s = sched.scheduler(time.time, time.sleep) # 实例化scheduler对象
def print_time(a='default'): # 要调用的函数
print("From print_time", time.time(), a)
def print_some_times():
print(time.time())
s.enter(10, 1, print_time) # 加入队列
s.enter(5, 2, print_time, argument=('positional',))# 加入队列
s.enter(5, 1, print_time, kwargs={'a': 'keyword'})# 加入队列
print(s.queue)
s.run()
print(time.time())
print_some_times()
# 1607676900.9483116
# From print_time 1607676905.9483757 keyword
# From print_time 1607676905.9483757 positional
# From print_time 1607676910.9485233 default
# 1607676910.9485233
3. django后端框架 版本3.2.5
参考链接:
菜鸟教程-django
Django官方教程 (推荐)
3.1 安装Django
- 使用
pip install Django==x.x
,安装自己需要的版本 - 测试是否可用:
import django print(django.get_version()) # 如果可用的话会打印出版本号,我这里是3.2.5
- 创建Django项目:
django-admin startproject mysite # mysite是项目名称。(这句是示例,后面图中我实际创建的项目名是django_app)
我们主要修改urls.py
和settings.py
文件
3.2 数据库
-
先设置好本地服务器环境,我使用的是phpstudy pro,启动mysql服务
使用上图中右上角的数据库工具
——SQL_Front
,新建一个数据库,数据库名为comments -
修改Django项目
settings.py
中数据库部分:
上图中NAME是数据库名,USER是数据库用户名,PASSWORD是数据库密码 -
使用
python manage.py startapp database
新建一个app,名字叫database,用于数据库操作。
进入database目录,修改models.py
,如下图设置好数据表类:
-
然后修改
settings.py
的INSTALLED_APPS
,添加上面新建的app,也就是database,如下图所示:
-
使用
python manage.py makemigrations database
和python manage.py migrate database
对该数据表进行初始化,也就是创建这些表-
makemigrations的官方解释如下:
-
migrate命令的官方解释如下:
-
运行上面两个命令之后,使用phpstudy pro的SQL_Front查看数据库,可以看到已经创建了我们刚才在
model.py
里定义的数据表
总之,模型更改的三步指南:
1. 在models.py
中更改模型。
2. 运行python manage.py makemigrations app名字
,存储这些更改。
3. 运行python manage.py migrate
,将这些更改应用到数据库。 -
-
修改
views.py
,编写返回数据表内容的函数
get_360实现了以json格式返回Comments_360
表中的所有数据 -
修改
urls.py
,也就是网站目录。
在终端输入python manage.py runserver
回车,运行django项目,实现了在http://127.0.0.1:8000/360/
中返回数据库中数据,如下图所示:
至此,后端部分完成。
4. 将后端部署到腾讯云和阿里云服务器上,进行生产环境部署
参考链接:【Django】宝塔面板部署Django+MySQL项目实战 这篇文章写的很详细,可以直接去看这篇文章
-
部署前准备
- 调开发模式为生产模式
Django配置文件settings.py
修改,调开发模式为生产模式:DEBUG = False # 开发模式为True,上线时设置为False ALLOWED_HOSTS = ['8.130.98.214'] # 开发模式为*,实际生产为真实IP或域名 STATIC_URL = '/static/' #上线模式 # STATICFILES_DIRS = [ # os.path.join(BASE_DIR, "static"), # 开发模式 # ] STATIC_ROOT = os.path.join(BASE_DIR, "static") # 生产模式
- 导出项目依赖包
pip freeze > requirements.txt
- 收集静态文件
python manage.py collectstatic
# 必须调到生产模式才能成功
其中带有后台的项目必须要收集静态文件,部署时才能显示后台。
- 调开发模式为生产模式
-
登录腾讯云控制台,设置云服务器系统,我这里选择的系统是腾讯云专享版宝塔Linux面板
-
登录宝塔面板,软件商店 — 下载安装MySQL、Nginx、Python项目管理器;在Python项目管理器中安装Python 3.8
-
添加站点
-
上传本地项目至站点文件夹下
-
编辑uwsgi.ini文件
-
配置数据库,修改项目配置文件
settings.py
中 MySQL配置 -
Python项目管理器添加项目
-
配置网站的配置文件