热爱技术
专注分享

python qq机器之 使用阿里云语音合成让机器人发送好听到炸裂的语音

misery阅读(125)

上一篇文章我们写了用python实现qq机器人的基本操作:

本篇文章,我们来尝试使用语音合成技术,让机器人发送语音消息。博主尝试了市面上大多数知名的语音合成服务商,最后选择了阿里云。先来听一下阿里云语音合成的效果:

博客介绍 – 女声1
新闻播报 – 女声2

一 、 开通阿里云语音合成

语音合成项目地址: https://ai.aliyun.com/nls/tts

开通免费版本后,进入控制台,创建一个新的项目:

图-创建项目

然后进入你的项目,切换到“语音合成”,选择一个你喜欢的声音,点“发布上线”(上方有一个appkey,请记下来):

图-项目发布上线

二、 阿里云接口调用之计算鉴权秘钥

阿里云的鉴权秘钥(Access Token)是对所有阿里云服务都适用的一种秘钥,就是说任意项目要请求阿里云的接口,都需要计算这个秘钥(区别于TTS接口鉴权)。我们在适用语音合成接口的时候,需要先用阿里云的开发者id和秘钥计算出鉴权秘钥,然后在通过这个鉴权秘钥组合语音合成(TTS)的appkey,来请求语音合成接口。因为鉴权秘钥存在过期时间,最好的解决方法就是,每次请求语音合成接口,都先计算一遍这个秘钥。


计算鉴权秘钥的官方文档:https://help.aliyun.com/document_detail/72153.html
为了方便我们使用SDK来操作。

Access Key: 阿里云开发者ID,又称为访问公钥。
Access Secret: 阿里云开发者秘钥,又称访问秘钥。
Access Token:通过Access Key 和 Access Secret 计算出来的鉴权秘钥,有时效性。
app key:项目的公钥,搭配Access Token来请求接口。

首先安装阿里云SDK核心库: 命令行执行 pip install aliyun-python-sdk-core

获取阿里云开发者ID和开发者秘钥:
https://usercenter.console.aliyun.com/manage/ak#/manage/ak (AccessKey和AccessSecret就是开发者ID和秘钥)

用python计算鉴权秘钥:

from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.request import CommonRequest
import json

def get_aliyun_secret():
    client = AcsClient('你的开发者ID',
                       '你的开发者秘钥',
                       'cn-shanghai'
                       )
    request = CommonRequest()
    request.set_method('POST')
    request.set_domain('nls-meta.cn-shanghai.aliyuncs.com')
    request.set_version('2019-02-28')
    request.set_action_name('CreateToken')
    r = client.do_action_with_exception(request)
    r = json.loads(r.decode())
    return r['Token'].get('Id')

三、 阿里云接口调用之语音合成

语音合成文档: https://help.aliyun.com/document_detail/94737.html

语音合成带有python的SDK,但是无法pip安装,比较麻烦,所以我们采用Restful API形式(http调用)。

http调用的方式,我们只需要用requests的post即可,将相关参数post到阿里云接口,即可返回音频。

import requests
import base64

def tts(text):
    data = {
        'appkey':'你的appkey',               #语音合成项目里的appkey
        "text": text,                       #要语音合成的文字
        'token':get_aliyun_secret(),        #上一步的鉴权秘钥
        'format':'mp3',                     #合成语音的格式
        "sample_rate": "16000",             #比特率
        "volume":'90',                      #音量
        "pitch_rate":'0',                   #语调
        "speech_rate":'-250',               #语速
        "voice":'Siyue'                     #发音人 参数详见 https://help.aliyun.com/document_detail/84435.html
    }
    header = {
        "Content-Type": "application/json;charset=UTF-8"
    }
    r = requests.post('https://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/tts',data=json.dumps(data),headers=header)
    return base64.b64encode(r.content).decode()

运行后会返回base64编码的语音。

四、使用酷Q pro

免费版的酷Q Air不支持发送语音,我们需要去下载一个酷Q pro,然后开个会员…
下载地址: https://cqp.cc/t/14901
然后按照上一篇文章的方法,配置酷Q http API。

五、发送语音

以发送群聊语音为例:

def send_record(group_id,text):
    api_url = 'http://127.0.0.1:5700/send_msg'
    data = {
        'msg_type': 'group',
        'group_id':group_id,
        'message':'[CQ:record,file=base64://{}]'.format(tts(text))
    }
    requests.post(api_url,data=data)

调用这个方法,传入qq群号,和要发送的文本,即可直接把文字转成语音发送到群聊啦。

六、 完整代码

from aliyunsdkcore.client import AcsClient
from aliyunsdkcore.request import CommonRequest
import json
import requests

accessKey = ''
accessSecret = ''
appKey = ''
botServerApi = 'http://127.0.0.1:5700/send_msg'


def get_aliyun_secret():
    client = AcsClient(accessKey,accessSecret,'cn-shanghai')
    request = CommonRequest()
    request.set_method('POST')
    request.set_domain('nls-meta.cn-shanghai.aliyuncs.com')
    request.set_version('2019-02-28')
    request.set_action_name('CreateToken')
    r = client.do_action_with_exception(request)
    r = json.loads(r.decode())
    return r['Token'].get('Id')


def tts(text):
    data = {
        'appkey':'你的appkey',               #语音合成项目里的appkey
        "text": text,                       #要语音合成的文字
        'token':get_aliyun_secret(),        #上一步的鉴权秘钥
        'format':'mp3',                     #合成语音的格式
        "sample_rate": "16000",             #比特率
        "volume":'90',                      #音量
        "pitch_rate":'0',                   #语调
        "speech_rate":'-250',               #语速
        "voice":'Siyue'                     #发音人 参数详见 https://help.aliyun.com/document_detail/84435.html
    }
    header = {
        "Content-Type": "application/json;charset=UTF-8"
    }
    r = requests.post('https://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/tts',data=json.dumps(data),headers=header)
    return base64.b64encode(r.content).decode()

def send_record(group_id,text):
    api_url = 'http://127.0.0.1:5700/send_msg'
    data = {
        'msg_type': 'group',
        'group_id':group_id,
        'message':'[CQ:record,file=base64://{}]'.format(tts(text))
    }
    requests.post(api_url,data=data)

if __name__ == '__main__':
    send_record(12345678,'今天天气真不错')

使用python打造一个自己的QQ机器人 【基础篇】

misery阅读(297)

webQQ在今年1月已经停止服务了。想用python写一个QQ机器人,但是python的QQbot库因为webQQ停止服务,挂得不能再彻底了。后来google了好几天,终于在github找到了一个神奇的插件=coolq-http-api

这个插件的神奇之处在于,它运行一个server,把qq信息转成http形式。发送,接收信息的时候,只需要使用http请求,即可和server进行交互,非常方便,也非常使用python(requests库无敌)。当然,插件运行基于酷Q,需要用酷Q加载插件才能正常使用。

要实现用python发送/接收消息,要用requests发送http请求之外,还要用flask在本地搭建一个flask服务端,告知插件flask服务器的地址和端口,这样所有的qq消息都会自动传递给flask,我们可以根据消息的来源,内容,自动判断是否要回复(用requests向酷Q进行http请求)即可。原理图如下:

一、 准备工作

  1. 酷Q下载安装: https://cqp.cc/t/23253 下载任意版本,右键解压即可使用。
  2. 插件下载安装: https://github.com/richardchien/coolq-http-api/releases 下载cpk文件,放到酷Q的app文件夹中
  3. python下载安装:这个就不说了,说太多次了,去官网下载安装包,一键安装就行了,注意要使用python3版本。
  4. python IDE下载安装:推荐使用pycharm,具体安装过程略。也是一键安装。

搞定后你的酷Q app文件夹下应该是这样的:

  • 启动酷Q主目录下的CQA.exe,打开酷Q登录你要做机器人的QQ号,建议使用小号,防止被封。
  • 由于酷Q使用的是手机TIM协议,成功上线后, 你可以看到小号为TIM在线。
  • 右键酷Q的小图标,进入应用管理,找到HTTP API直接启用。

成功启用后,会弹出一个控制台窗口,提示正在监听5700端口。

二、 配置酷Q httpApi插件

插件的配置文档: http://richardchien.gitee.io/coolq-http-api/docs/
配置文件在酷Q Air\data\app\io.github.richardchien.coolqhttpapi\config 文件夹下,是一个以QQ名称命名的json文件。
以下几项配置比较关键:

  • port : 酷Q server监听的端口,可自定义。
  • use_http:务必是true,除非你使用websocket。
  • post_url:flask服务端接收消息的接口,可自定义。
  • post_message_format:酷Q向flask发送消息的格式,建议改成array。

其他的配置项可见文档: https://richardchien.gitee.io/coolq-http-api/docs/4.12/#/Configuration?id=%E9%85%8D%E7%BD%AE%E9%A1%B9
附上自己的简单配置:

三、用python发送自己的第一条QQ消息

先悄悄用pip安装一个requests库….
插件的api文档: https://richardchien.gitee.io/coolq-http-api/docs/4.12/#/API
我们看下发送私聊消息的接口:

用requests构造一个http post请求,post的数据填入相关信息:

import requests

data = {
    'user_id':723690032,
    'message':'我是一个可爱的小机器人喵~',
    'auto_escape':False
}

api_url = 'http://127.0.0.1:5700/send_private_msg'
#酷Q运行在本地,端口为5700,所以server地址是127.0.0.1:5700

r = requests.post(api_url,data=data)
print(r.text)

四、接收第一条QQ消息

同样先悄咪咪安装一个flask….

from flask import Flask,request
from json import loads

bot_server = Flask(__name__)

@bot_server.route('/api/message',methods=['POST'])
#路径是你在酷Q配置文件里自定义的
def server():
    data = request.get_data().decode('utf-8')
    data = loads(data)
    print(data)
    return ''

if __name__ == '__main__':
    bot_server.run(port=5701)
    #端口也是你在酷Q配置文件里自定义的

运行一下这个flask程序,然后用自己的QQ给你的机器人随便发条信息:

{'font': 65320440, 'message': [{'data': {'text': '你才不是可爱的小机器人呢'}, 'type': 'text'}], 'message_id': 8, 'message_type': 'private', 'post_type': 'message', 'raw_message': '你才不是可爱的小机器人呢', 'self_id': 766202408, 'sender': {'age': 0, 'nickname': 'Misery_', 'sex': 'unknown', 'user_id': 723690032}, 'sub_type': 'friend', 'time': 1573044578, 'user_id': 723690032}

控制台打印的内容是一个python的字典格式,我们可以优化一下,取出需要的信息,如QQ号,昵称,消息内容:

QQ = data.get('user_id')
nickname = data['sender'].get('nickname')
message = data['message'][0]['data'].get('text')
print('来自QQ:{},昵称:{} 的信息:\n{}'.format(QQ,nickname,message))

五、后记

到这里,你就写出了一个hello world QQ机器人,想实现更多的功能,需要多去研究研究API,多研究研究python。这个插件功能相当强大,几乎涵盖了所有的QQ功能。博主曾经写过一个,可以对接某面板,涵盖登录,查询,支付等多种功能,并带有群管模块,接入阿里云tts语音系统,实现语音聊天。真的非常强大,有兴趣的同学可以好好研究一下。

友情分析某刷步网站,开启微信永久自动云刷步模式

misery阅读(1177)

最近偶然间发现了某个刷步网站,可以实现在线给微信刷步。http://vwshuabu.com/

这个网站可以实现网页刷步,不需要手机任何设置,把号挂在网页就可以实现云刷步了,感觉挺神奇的。抱着研究的态度,花了3块钱研究了下这个网站,找到了刷步的接口(相当弱)。这篇文章,就来讲一讲怎么找到这个接口,以及如何自己写程序,开启自动化云刷步。

一 分析刷步的接口

首先进入这个网站,选上方的 ‘网页刷步’,先花三块钱去买一个体验卡。

file

然后他会给你一个登录地址,和一个卡号:

file

我们去给的地址,选小秘书,然后输入你的卡密来激活。

file

激活完了点开始,来扫码登录你的微信:

file

完了会进入主菜单,在下面可以看到一个微信步数设置,还有一个自动化步数:

file

它这个自动化设置步数用起来有点问题,而且只支持设置最长一周,有点鸡肋。这里我们用直接设置步数的模式,写个程序来‘智能化’设置步数,让它看起来更加合理。

在这个页面,我们打开F12,切到network标签,直接设置一个比当前步数大的数字,来看下执行结果。

file

file

file

可以看到这是一个post的请求,post的数据只有「code」和「num」两个参数,分别对应的是你买的卡号和要设置的步数。这里要强调的是,这个网站似乎没对卡密做过期时间的核对,仅仅是卡密过期了不让你打开网页而已,对于http请求没有任何影响。

这就非常棒了,我们用python写一个程序,然后让程序每天定时发送请求,模拟正常的步数即可。

二 整理接口

1: 请求登录二维码接口:https://nb.fx7899.com/fetchimage?img=你的卡号

2: 设置步数接口: https://nb.fx7899.com/setRun/ 参数为卡号和步数

三 分析逻辑

这个程序其实相当简单,只要定时用requests来post一下你的步数就行了。为了让步数看起来更加科学,我们就需要使用随机步数+分段时间了。

这里我的方法是这样的:一天的时间从早上7点开始,到晚上22:40结束,把时间分为早,中,晚三段。每天随机一个10000-25000的数字作为步数,早上固定xx步-xx步,中午和晚上的步数占一个随机的百分比,最后,所有步数加起来等于总步数。然后在时间段内,每隔10分钟加一次步数,让步数做到缓慢增长。

最后在做一个定时任务,每天重复执行一次即可。

代码实现

设置步数:

import requests
import schedule

def add_step(step:int):
    url = 'https://nb.fx7899.com/setRun'
    header = {
        'accept': 'application/json, text/javascript, */*; q=0.01',
        'accept-language': 'zh-CN,zh;q=0.9',
        'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
        'origin': 'https://nb.fx7899.com',
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36',
        'x-requested-with': 'XMLHttpRequest'
    }
    data = {
        'code': '',
        'num': step
    }
    r = requests.post(url,data=data,headers=header)
    success = r.json().get('status')
    tip = r.json().get('message')
    return success,tip

随机生成各个时间段的步数

def get_schedule():
    all_step = random.randint(10000,25000)
    wake_up_step = random.randint(1000,1500)
    morning = random.randint(300,400)/10
    afternoon = random.randint(350,450)/10
    night = random.randint(150,350)/10
    rest_step = all_step - wake_up_step
    morning_step = rest_step*morning/100
    afternoon_step = rest_step*afternoon/100
    night_step = rest_step*night/100
    return wake_up_step,morning_step,afternoon_step,night_step,all_step

判断当前的时间段

def get_now_duration():
    now = datetime.now()
    t = int(now.strftime('%H%M'))
    if t in range(700,731):
        return 1,t
    elif t in range(830,1231):
        return 2,t
    elif t in range(1400,1731):
        return 3,t
    elif t in range(1900,2230):
        return 4,t
    else:
        return 0,t

执行刷步

def main_handle():
    wake_up_step,morning_step,afternoon_step,night_step,schedule_step = get_daily_schedule()
    all_step = 0
    while True:
        duration,now = get_now_duration()
        if now > 2330:
            return
        if duration == 1:
            all_step += int(wake_up_step/3)
        elif duration == 2:
            all_step += int(morning_step/24)
        elif duration == 3:
            all_step += int(afternoon_step/21)
        elif duration == 4:
            all_step += int(night_step/21)
        else:
            continue
        add_step(all_step)
        sleep(599)

最后用一个定时任务库来每天开启一次

if __name__ == '__main__':
    schedule.every().day.at('00:01').do(main_handle)
    while True:
        schedule.run_pending()
        sleep(59)

四 后续

这个接口没有对卡号过期时间进行验证,所以我们只要买一张体验卡,就可以无限使用了。如果你想用其他的功能,同样的道理,F12,点击那个功能后,分析下请求地址,请求参数,用python或者postman直接构造请求就可以了。

在刷步期间请不要登录电脑或者ipad端的微信,这样会导致刷步掉线。如果你不慎顶号了,请看本文第二步中,那个微信登录二维码请求地址,直接在浏览器打开扫码登录即可。

这个东西也是无意间发现的,能用多久也不知道,就当娱乐了吧~

【肥宅快乐贴】使用百度人脸识别API,对爬取的小姐姐进行颜值排序

misery阅读(4038)

在上一篇文章中,我们用python实现了对图片网站上的小姐姐图片爬取:

【肥宅快乐贴】用python制作爬虫批量下载图片站上的漂亮小姐姐

这篇文章就来演示一下,对爬取下来的小姐姐图片进行颜值判定,然后根据颜值高低来对图片进行排序。

颜值判定的API是百度的,这个API我测试过好多图,准确度还蛮高,且免费不限制使用次数(限速每秒2次),毕竟百度是国内大厂 (~ ̄▽ ̄)~。也希望大家手下留点情,不要把百度人脸识别API和小姐姐图片站给玩坏了

百度人脸识别项目:http://ai.baidu.com/tech/face/detect

环境准备:

python3,requests模块,百度人脸识别API。

python3怎么装我就不说了,requests模块的话,在python环境的电脑里,直接打开cmd,执行pip install requests 就可以了。

人脸识别API:打开http://ai.baidu.com/tech/face/detect 点立即使用,登录账号后创建一个应用,名字随便写:

image.png

创建完成后,在“管理应用”里面找到你的APPid,API key,Secret key就可以了。

image.png

一、逻辑分析

在正式开始写之前,需要把逻辑给分析清楚,然后根据这个逻辑,来编写相应的代码。在本篇文章的这个程序中,首先要把图片上传到百度云人脸识别服务器中进行识别,然后过滤掉没有人脸的图、男性的图和人脸置信度过低的图,然后再对图片进行颜值排序。

流程如下

① 遍历本地文件夹中的图片

② 将本地照片上传到百度云的人脸识别服务器中,返回识别参数。

③ 分析返回的参数,过滤掉不是人脸的图片

④分析返回的参数,过滤掉男性的图片

⑤ 分析返回的参数,过滤掉人脸置信度过低的图片

⑥从返回的参数中取出图片的颜值,加上这张图片对应的路径,统一存放到一个字典中,格式为 {路径:颜值}

⑦ 循环取出字典中颜值最大的元素的路径和颜值,删除最大值,复制到新的文件夹,然后重命名文件为 排名+颜值。

二、分析百度人脸识别API

文档参考:http://ai.baidu.com/docs#/Face-Detect-V3/top

根据文档描述,可以看到,需要先使用你的API key和Secret id请求鉴权秘钥,然后再通过鉴权秘钥去请求人脸识别服务器。

1.计算鉴权秘钥(access_token)

image.png

运行结果:

image.png

这一串东西就是我们需要的鉴权秘钥。

2.发送图片到百度的人脸识别服务器

因为本次程序是识别本地的图片,所以我们不考虑文档中的网络图片和图片face_id的情况。

根据文档,我们需要发送的内容有:access_token(放在url参数里),图片的base64编码和图片的类型,请求的参数:beauty(颜值)gender(性别)。发送方式为 post。我们以识别下面这张图的小姐姐为例:

《路人女主的养成方法》加藤惠和服场照-1.jpg

image.png

运行一下,成功返回了:

image.png

 

3.处理返回的数据

上一步中完整的返回数据如下:

{'error_code': 0, 'error_msg': 'SUCCESS', 'log_id': 7594757925058, 'timestamp': 1538733924, 'cached': 0, 'result': {'face_num': 1, 'face_list': [{'face_token': '8cea6b27151c7e5253d501fb14cda5fc', 'location': {'left': 342.3506775, 'top': 111.2269287, 'width': 89, 'height': 84, 'rotation': -16}, 'face_probability': 1, 'angle': {'yaw': 8.847219467, 'pitch': 6.044753075, 'roll': -17.03232384}, 'beauty': 65.53157043, 'gender': {'type': 'female', 'probability': 0.9999910593}}]}}

 这是一组字典嵌套列表嵌套字典嵌套字典的数据。我们需要的数据如下:

error_code:判断是否成功识别 【0为成功,其他code为不成功】

result里的face_list里的的第1个元素中的 face_probability 【0-1,越大越可能是人脸】

result里的face_list里的的第1个元素中的 beauty 【0-100,越大颜值越高】

result里的face_list里的的第1个元素中的 gender 中的 type 【性别,male为男性,female为女性】

这些元素的访问方法如下:

image.png

运行一下看结果:

image.png

这里就获取到了我们需要的信息。

三、完整代码的实现

import requests
from json import loads
from base64 import b64encode
import os
import shutil

API_key = '改成你自己的'
Secret_key = '改成你自己的'

#根据文档,定义获取access_token的url地址,传入API key和Secret key
token_url = token_url = 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id={api_key}&client_secret={api_sec}'.format(api_key=API_key,api_sec=Secret_key)
#请求秘钥,获得返回的文字内容
token_request = requests.get(token_url).text
#将返回的内容格式化为字典形式,并访问字典中"access_token"的值
access_token = loads(token_request)['access_token']

#写一个函数
def AiFace(img_path):

    url = 'https://aip.baidubce.com/rest/2.0/face/v3/detect?access_token=' + access_token       #请求的url,加上access_token参数
    img = open(img_path,'rb').read()    #二进制方式读取图片
    img_b64 = b64encode(img)     #将图片转为base64编码
    #发送的数据
    data = {
        'image':img_b64,      #图片的base64编码
        'image_type':'BASE64',  #图片类型
        'face_field':'beauty,gender',   #需要返回的参数
        'max_face_num':1    #每张图片中最多返回一张人脸
    }
    r = loads(requests.post(url,data).text)     #请求一下,然后把返回的内容转化为字典形式

    if r['error_code'] == 0:    #如果识别成功,函数结束,return出结果
        return r['result']['face_list'][0]
                    #如果不成功,那么啥都不return(None)

####开始运行

img_folder = 'd:/test/'     #图片文件夹
save_folder = 'd:/result/'  #结果保存的文件夹

#
for root,path,files in os.walk(img_folder):     #遍历图片文件夹
    info_dic = {}
    count = 0
    for file in files:
        complete_path = os.path.join(root,file)     #获得文件的完整路径
        if '.jpg' in complete_path:     #去除不是jpg的文件
            count += 1
            print(str(count)+'-检测--->'+complete_path)
            result = AiFace(complete_path)  # 实例化函数
            if result != None:      #去除识别错误的文件(没有人脸)
                face_probability = result['face_probability']  # 是人脸的可能性
                beauty = round(result['beauty'], 2)  # 颜值,保留二位小数
                gender = result['gender']['type']  # 男女
                if face_probability >0.75:  #去除人脸置信度小于0.75的图片
                    if gender == 'female':  #去除不是女性的图片
                        info_dic[complete_path] = beauty    #结果保存到字典中 {文件路径:颜值}
                        print('---检测到人脸,颜值'+str(beauty)+'分---')

os.makedirs(save_folder)
face_count = len(info_dic) #计算字典长度

for i in range(1,len(info_dic)+1):  #遍历字典中的内容
    max_beauty_file = max(info_dic,key=info_dic.get)    #找到最高颜值对应的key(文件路径)
    beauty_score = info_dic[max_beauty_file]        #找打到最高颜值
    img_name = str(i)+'-('+str(beauty_score)+'分).jpg'
    shutil.copy(max_beauty_file,save_folder+img_name)   #复制到新文件夹并改名为排名+颜值分
    info_dic.pop(max_beauty_file)     #删除字典中的这个元素
    i+=1

print('\n完成,共检测'+str(count)+'张图片,其中'+str(face_count)+'张有人脸')
print('颜值排序已保存到:'+save_folder)

上面每一步都注释了,这里的颜值排行主要通过字典排序的算法实现。

随便找几张图测试一下:

image.png

运行结果:

image.png

排序文件夹:

image.png

【肥宅快乐贴】用python制作爬虫批量下载图片站上的漂亮小姐姐

misery阅读(2400)

本来想另外找一个漂亮小姐姐的图片站,可是百度了一下,那些网站的图片尺度略大,我的博客又是备案过的,不敢瞎搞,所以还是找了一个中规中矩的cosplay网站来写这篇文章。这个网站之前我用火车头爬过,看之前的文章:

简单使用火车头采集器批量采集整站网页图片(解决闪退)

不过用这种爬虫软件来爬,说实话不如自己写程序来得方便。今天就来演示一下怎么用python来爬这个网站的图片。当然这篇文章的方法也适用于其他的图片站,大家学会了就可以自由发挥了(~ ̄▽ ̄)~。

准备工具:python环境的电脑,python的requests模块和beautifulsoup4模块,谷歌浏览器,有一点代码基础。

目标站:http://www.cosplaymore.com/

目标:爬取cos板块所有帖子中的图片。

一、环境配置

这篇文章我们用的是python3环境,可以直接去python官网下载:https://www.python.org/downloads/

下载完了直接安装就行。requests模块beautifulsoup4模块直接在cmd里执行pip install requests 和 pip install beautifulsoup4就行了。

这里不多说,如果连安装都有问题,那么下面可能就不用看了。

二、分析网页

爬虫的本质其实是找到网页的规律,然后通过这个规律去编写一个适应这个规律的程序,然后通过这个程序来获取我们需要的东西。所以首先我们要分析网页,来找到这个规律。

1.分析第一级页面规则,获取所有帖子的链接。

打开谷歌浏览器,进入我们需要爬取的网站,切换到 cos 这一栏。我们今天就来爬cos这一栏里所有帖子的图片。

可以看到网址是一个比较规则的地址,我们把网址后缀的 "30-1.html"改为“30-2.html”,页面就会跳转到第二页。

image.png

观察一下网页排版,看下我们需要爬取的东西和不需要的东西。

image.png

我们需要的是下面帖子区域的内容,一共有98页2729个帖子

image.png

右键其中一个帖子,然后右键“检查”,在右边弹出的任务栏里,鼠标往上移动,直到移动到一个位置,网页全选了整个帖子的部分,这个被选中的标签 <div class="con">就是帖子区域的的标签层,包含了整个帖子区域。在这个层里,包含了所有的帖子链接。

image.png

可以看到,这个div层下每个<li>标签都包含了一个帖子:

image.png

再展开其中一个<li>标签,看到它包含了这个帖子的2个链接和1个缩略图链接,我们需要的是是帖子链接。

这里我们就找到了帖子排布的规律,那么第一级页面的规则也基本分析完成了。规则如下:

① 通过改变网页链接,实现翻页。

② 找到<dlv class="con">这个标签。

③找到②中div包含的所有<li>标签中的链接。

一级页面爬取后,即可获得cosplay板块所有帖子的链接。

2.分析二级页面规则,获得所有图片的链接。

随便打开一个帖子,同样右键一张帖子里的图片,然后右键“检查”。往上移动鼠标,直到整个帖子区域被选中:

image.png

这个class="PiccontentPart fl 的div标签,里面包含了整个帖子的图片内容。同样,可以看到这个div下的每个<p>标签里包含的就是每张图片的信息。可以看到,<p>标签里很简单粗暴地包含了一个图片地址。

image.png

那么二级页面的规则也分析完了。规则如下:

① 通过一级页面获得的帖子链接,进入帖子,找到 class="PiccontentPart fl 的div标签

② 找到上面div标签中的<p>标签

③ 获得<p>标签中的链接

三、开始写python程序

上面我们安装的requests模块用于网页访问,beautifulsoup4模块用于网页解析。

1.获取第一级页面并解析出帖子中的链接

image.png

注释在程序里已经写好了,其中的requests的get传递的参数headers可以不填,这个是拿来表明身份的,表示你是浏览器而不是python程序。

运行一下效果图:

image.png

一共爬到了2729个帖子链接,一共98页,每页是28个帖子,数量刚好差不多。

2.解析二级页面:帖子中图片的地址


image.png

这里我贴了其中两个帖子做测试,结果如下:

image.png

当然我们也可以改进下,找到帖子的标题,也是层级查找:

image.png

3.合并完整程序

最后我们把两个查找合并嵌套一下,获取所有帖子中的所有图片的链接,控制下访问速度,再做下错误处理并完善下提示。

完整代码如下:

import requests
from bs4 import BeautifulSoup
from time import sleep

header ={'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
         'cookie':'UM_distinctid=1663384985d6fd-03db9719bc459f-8383268-1fa400-1663384985f5c5; CNZZDATA1256110375=1639678596-1538460026-%7C1538460026; pgv_pvi=6300302336; pgv_si=s7801597952; yunsuo_session_verify=61e64cd8bf75fa31a03c96c3195f46f5'}

topic_count = 0
img_count = 0
err_count = 0
for i in range(1,99):
    try:
        #根据规律遍历生成98个页面地址
        url = 'http://www.cosplaymore.com/list-30-'+str(i)+'.html'
        #使用requests的get方法访问这98个页面
        r = requests.get(url=url,headers=header,timeout=30)
        #将返回的页面内容通过beautifulsoup的网页解析器解析出来
        r_html = BeautifulSoup(r.text,'html.parser')
        #查找class名字为'con'的div
        html_board = r_html.find('div',class_='con')
        #遍历class名字为‘pic imgholder’的a标签
        for topic_link in html_board.find_all('a',class_='pic imgholder'):
            sleep(5)
            topic_count+=1
            print('访问第'+str(topic_count)+'个帖子')
            # 访问每一个帖子
            r_topic = requests.get(url=topic_link.get('href'), headers=header, timeout=30)
            # 将帖子内容解析为html网页
            topic_html = BeautifulSoup(r_topic.text, 'html.parser')
            # 找出帖子的标题
            topic_title = topic_html.find('div', class_='PiccontentTitle').find('font').text
            # 找出这个帖子中的贴图区域
            topic_board = topic_html.find('div', class_='PiccontentPart fl')
            # 从贴图区域找出所有的img标签
            title_count=0
            for img_link in topic_board.find_all('img'):
                # 从img标签中获取链接
                #print(img_link.get('src'))
                title_count += 1
                img_count += 1
                #print(topic_title+str(title_count))
                #下载图片保存到d盘的img文件夹
                img_file = requests.get(url=img_link.get('src'),headers=header,timeout=30)
                img_path = 'd:/img/'+topic_title+str(title_count)+'.jpg'
                with open(img_path,'wb') as f:
                    f.write(img_file.content)
                    print('图片已保存=====>'+img_path)
    except:
        err_count+=1
        print('出错,跳过'+str(err_count))

print('共爬取到'+str(topic_count)+'个帖子链接')
print('共爬取到'+str(img_count)+'张图片地址')
print('出错的帖子:'+str(err_count)+'个')

效果如下:

image.png

image.png

四、后记

        这篇文章的例子爬取还是很简单的,结构很清晰,通过class名字就能直接找到标签,是比较基础的那种,也是为了便于大家理解。实际情况中,多数网站做了反爬虫处理,除了class名随机,标签的排列也不一定是固定的顺序。且不同的帖子结构不一样,会让你很难下手。不过对于python来说,这都不是问题,可以通过正则匹配来解决这个问题。爬取的内容也可以通过list或者dic等方式进行处理,真不行还能使用pymysql进行数据去重和整理。爬虫爬取的方式千变万化,唯有核心思想不会变:根据网页结构的规律,写出适合这个规律的程序。

        多数网站也做了访问速度处理,如果你访问速度过快就会封你的ip。所以爬虫也需要适时限速,爬一个帖子休息几秒钟,反正写好了代码后台运行,让它挂着运行就好了。

beautifulsoup4模块官方文档:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html

requests模块官方文档:http://docs.python-requests.org/zh_CN/latest/user/quickstart.html

python3对接godaddy API,实现自动更改域名解析(DDNS)

misery阅读(8722)

godaddy API文档https://developer.godaddy.com/

文章开始前,先解释下如下问题:

①什么是域名解析?

域名解析一般是指通过一个域名指向IP地址(A解析),然后我们访问这个域名就可以有直接访问这个IP地址的效果,只需要记住域名即可,无需记住IP。

②什么是DDNS?

DDNS是动态域名解析,一个域名可以根据IP的变化,自动修正解析,无论设备的IP地址怎么变化,这个域名将一直指向对应的设备。

③有什么用?

动态域名解析广泛应用于家庭网络,因为家庭网络的IP地址是动态的,每次重启猫,都会造成IP地址变化。如果想要通过外网稳定访问(不一定是web访问)家里的设备,就需要使用动态域名解析,现在用的比较广泛的是花生壳DDNS。

④为什么要用godaddy?

首先godaddy提供更改域名解析的API,其次godaddy是世界上最大的域名服务商,再次,博主试过百度云加速、阿里云DNS、腾讯云DNS、无一例外都不提供API。没有API的情况下,就需要通过定时登录获取cookie,然后再模拟更改域名的网页操作,post数据到服务器。在使用godaddy之前博主试过post上述国内域名申请商,无一例外post成功后就限制登录,具体原因未可知。

前言

在之前的一篇文章:

将你的电脑变成web服务器之三:使用python3监测公网IP,实现DDNS

    使用python3监控公网地址,然后上报给服务器,服务器再修改反代理/发邮件通知用户。由于最近使用远程桌面比较多,每次远程前都要打开邮箱找最近的一次公网地址变更记录。于是想如果可以实现动态域名解析呢?这样申请一个域名,随时都指向最新的IP地址,岂不是很方便?

    在前面问答里也说了,通过百度云加速、阿里云、腾讯云等域名解析,每次post成功数据,解析修改完了,账号就被限制登录了。因为这种post是完全模拟网页操作,可能被检测到了,然后就封号。如果有域名服务商可以提供API,直接通过API更改解析,不会封号,也不会限制操作导致失败,那不是美滋滋?

    于是顺手搜了下godaddy(最大的域名服务商)的API,果然有这个东西。在API的文档里,找到了这个东西:

image.png

可以直接更改一个域名的所有解析记录。不过参数看起来挺复杂,没关系,我们一点一点说。

 

一、购买一个域名

如果你有域名了,那这一步操作可以忽略掉。

详细步骤不说了,很简单,注册一个godaddy账户,然后搜索你想要的域名,比如我申请了一个域名为:pcserver.me,点DNS,可以看到域名解析的记录:

image.png

image.png

 

二、获取API的key

key是用来认证账户身份的,和浏览器的cookie一样,不同的是cookie会过期,而key可以永久不过期。在一段请求中,只要在头部包含这个key,就可以让服务器认定你的身份。

key申请地址:https://developer.godaddy.com/keys  登录账户,点网页右上角的“cereate new API key”,随便命名,环境选择 production

image.png

它会给你一个公钥key,和一个私钥secret,复制下来保存好:

image.png

 

三、文档中更改域名解析put的用法

地址:https://developer.godaddy.com/doc/endpoint/domains#/v1/recordReplace

image.png

可以看到,需要提供的参数是domain,还有records。domain就是我们需要修改解析的域名,如本文domain就是pcserver.me。records就是put请求向浏览器发送的参数,里面有data、name、prot等参数。其中要用到的有以下几个参数:

data :解析记录。如将www解析到3.3.3.3,那么data就是3.3.3.3

name :解析名就是所说的域名前缀,如www.pcserver.me,name(解析名)就是www,如果为@则表示为空

ttl :域名解析生效时间。域名解析提交后,多久才能生效,当然越短越好,最短600

type:解析类型。 一般用A解析,将一个域名指向一个IP地址。

records的数据以json形式传递。我们试一下填写数据,看官方为我们生成请求的格式:

image.png

生成格式:

image.png

这是一个linux的curl格式,我们需要把它转成python3格式。从官方生成的请求格式中可以看到,请求类型为put,-H后面为headers(请求头),包含accept、content-type、authorization。其中accept是请求返回接收的数据格式,content-type是发送的数据格式,authorization是用户认证(API秘钥),格式为:sso-key 你的key:你的secret,请求地址为:https://api.godaddy.com/v1/domains/pcserver.me/records (正式环境去掉ote)

 

四、使用python3实现

这里我们实际操作一下,通过API,将pcserver.me这个域名(没有前缀,解析名为@)直接定向到IP地址:5.5.5.5。详细解释已经注释在代码里了

#导入需要的模块
import urllib.request
import json

#这里做个示范,读取用户输入
ip_addr = input(str('输入IP地址:'))
#定义请求地址
api_url = 'https://api.godaddy.com/v1/domains/pcserver.me/records'

#直接做一个函数,传入API地址和更改的IP
def update_NS(api_url,ip_addr):
    #定义http请求头
    head = {}
    #定义服务器返回json数据给我们
    head['Accept'] = 'application/json'
    #定义我们发送的数据为json
    head['Content-Type'] = 'application/json'
    #定义身份认证信息
    head['Authorization'] = 'sso-key xxxxxxxxx你的key xxxxxxxxx:xxxxxxxxxx你的secret xxxxxxxxxx'

    #定义解析记录
    records_a = {
    "data" : ip_addr,
    "name" : "@",
    "ttl" : 600,
    "type" : 'A',
    }
    #下面这两个必须包含,不可更改
    records_NS01 = {
    "data" : "ns07.domaincontrol.com",
    "name" : "@",
    "ttl" : 3600,
    "type" : "NS",
    }

    records_NS02 = {
    "data" : "ns08.domaincontrol.com",
    "name" : "@",
    "ttl" : 3600,
    "type" : "NS",
    }
    #定义需要发送给服务器的数据为put_data这个列表,包含上面的解析记录
    put_data = [records_a,records_NS01,records_NS02]

    #错误处理
    try:
        #定义请求,包含请求地址,请求头,请求方式,并把put_data从json转换为字符串格式,再转换成bytes
        req = urllib.request.Request(api_url,headers = head,data = json.dumps(put_data).encode(),method = "PUT")
        rsp = urllib.request.urlopen(req)
        #根据官方文档我们只需要知道服务器返回码即可,200为成功,这里获取服务器的返回码
        code = rsp.getcode()
        #判断是否成功
        if code == 200:
            print('成功更改域名解析:'+ip_addr)
        else:
            print('更改失败!')
    #原谅我偷懒。官方有400/401/422等错误,这里统一处理了
    except:
        print('错误!')

#执行一下函数,并传入请求地址和我们输入的IP
update_NS(api_url,ip_addr)

执行一下看看效果:

image.png

image.png

这个模块可以配合:

将你的电脑变成web服务器之三:使用python3监测公网IP,实现DDNS

文中的监测公网地址,实现自动更改解析记录。以后就不要翻邮箱找IP啦。

 

五、几个坑

①这个put请求更改解析记录,会将整个域名的所有解析都替换成你put上去的内容。也就是说,如果我的域名 pcserver.me里面有其他解析记录,那么会将它们全部删掉。在put成功的一瞬间,我的加速器集体宕机,一看原来是加速器节点全部通过域名访问数据库,解析记录没了瞬间连不上数据库了。当然程序可以继续改进,因为有get方式可以请求当前的解析记录,做一下对比就好了。

②json数据通过put请求出去的话,需要用jsondumps转换成字符串,再把字符串转换成bytes,才可以发送,这个坑折腾了好久。

③目前只通过返回码的判断是否成功,这个程序试了N次都get不到返回数据,求大佬指点?

④godaddy的中国电话客服,客服妹子很一般。首先告知无法转接技术,当我询问API文档里那几个参数的意思后,查了5分钟没给回话,然后直接挂机了。。挂机了。。。挂机了。。。。

【超详细】使用python3做一个爬虫,监控网站信息下篇(循环对比,发送邮件)

misery阅读(3655)

接上文:

【超详细】使用python3做一个爬虫,监控网站信息(一)

上文我们已经利用python3的urllib模块和BeautifulSoup模块实现了这几个页面的抓取和分析提取信息。接下来要做的就是对抓取的信息进行循环比较,能体现出更新提示。上文中我们也说了,这个程序分为:信息提取模块(从网页中提取有用信息)、对比模块、还有邮件模块。这么多模块如果要直接写在程序里,一旦需要循环调用,那代码量可以翻好几倍。这里我们开始使用python的函数。

函数可以看做一个黑箱子,有输入和输出。输入东西(传递参数),函数处理完毕之后输出(return),在需要的时候,直接调用函数处理即可,无需再写代码。比如上一篇文章里,最后实现的功能,就是输入url_list,输出抓取到的多个页面的需要的信息。

 

一、把上一篇文章的代码封装成函数

我们先把上一篇文章的代码封装成两个函数,第一个函数输入url,return出当前url需要提取的信息

#定义一个名为get_webInfo的函数,传入参数url
def get_webInfo(url):
    req = urllib.request.Request(url)
    rsp = urllib.request.urlopen(req)
    html = rsp.read().decode('utf8','ignore')
    html = BeautifulSoup(html,'html.parser')
    for link in html.find_all('a',limit=3):
        info_link = link.get('href')
        info_text = link.get_text(strip=True)
    #函数执行完后,return(输出)执行的结果
    return info_text+'\n'+url[:-50]+info_link+'\n'

另一个函数输入url_list,调用get_webInfo函数,return出所有url需要提取的信息:

def parseWeb(url_list):
    初始化result为一个列表
    result = []
    for url in url_list:
        #每循环一次,就调用get_webInfo,传入参数url,解析出结果,存入变量webInfo
        webInfo = get_webInfo(url)
        print(webInfo)
        #每循环一次,就将解析结果放入result列表中
        result.append(webInfo)
    函数执行结束后,return(输出) result
    return result

 

二、写一个循环对比函数

在上一步中,我们封装了2个函数,get_webInfo()parseWeb,其中parseWeb()中调用了函数get_webInfo(),相当于执行parseWeb()并传入参数url_list,就可以实现多个页面第一条信息的获取并解析。那么接下来我们可以开始对解析出来的结果进行循环对比了。再看一遍程序流程图:

从图中可以看到,程序开始运行的时候,会抓取一次页面。这里我们运用定义一个全局字典tmp,保存抓取的结果,并跟最新抓取的结果进行对比,写最外面的大循环,判断是否为第一次执行。

先定义(初始化)一个字典tmp作为存储中间内容,值为None (空),再定义一个函数check(),用于循环对比,写最外层的大循环:

#定义一个字典,包含一个空元素history
tmp = {'history':None}
#定义一个比对函数check()
def check():
    #判断字典内的元素history不为空
    if (tmp['history']):
        #把临时值给tmp字典内的history元素 用于循环比较
        hisroty = tmp['history']
        now = parseWeb(url_list)
            ...比较过程...
        #比较完成后把值覆盖传递
        tmp [history] = now
    else:
        #如果tmp里的history元素为空则判定第一次运行
        tmp['history'] = parseWeb(url_list)

这样就完成了一个基本的逻辑,可以对上一次对比和本次获取到的内容进行循环对比了。接下来我们要做的是,在tmp不为空(非第一次运行)的情况下,判断信息是否更新,当然,使用if判断就行了。

首先为了排除因为网络原因导致的数据错误(获取不到数据、数据获取缺失等情况),需要对前后次获取的列表的长度(列表内字符个数)进行判断,比如本次抓取10个页面的第一条信息,那么列表长度固定为10。以下为比较过程:

#大前提,history的列表长度等于now的列表长度
if len(history) == len(now):
    #定义一个空的变量 result
    result = ''
    #使用zip函数,对history和now函数按顺序进行对比
    for a,b in zip(history,now):
        if a == b:
            print('未发现更新!')
        else:
            print('发现更新')
            #等价于result=result+b 发现更新就在变量result内添加内容,不会覆盖
            result+=b
            
            ...发送邮件...
            
    #注意空格,上面for循环执行后才会执行下面的if判断
    #为防止误判,两次获取内容都为空也满足len(history)==len(now),对result进行非空判断
    if result != '':
        #输出结果
        print('更新内容如下:'+result)
else:
    print('数据错误!')

 

三、使用smtp发送邮件

pyhon3自带email和smtplib模块,可以使用smtp发送邮件。由于云服务器默认屏蔽25端口,这里就使用465端口来发送加密邮件。

这里我们定义一个发送邮件的函数 send_mail(),传入参数邮件标题(title)、邮件正文(article)、邮件接收人(receiver)。由于只需要发送文本内容,所以邮件模块比较简单:

#导入需要使用的模块
import smtplib
from email.header import Header
from email.mime.text import MIMEText

#定义发送邮件的函数,传入title,article,receiver参数
def send_mail(title,article,receiver):
    #定义邮件地址、账号密码等变量
    host = 'smtp.mxhichina.com'
    user = 'admin@xiaoweigod.com'
    passwd = 'xxxxxxxxxx'
    sender = user
    coding = 'utf8'
    #定义message变量,写邮件内容、邮件头
    message = MIMEText (article,'plain',coding)
    message ['From'] = Header(sender,coding)
    message ['To'] = Header(receiver,coding)
    message ['subject'] = Header(title,coding)
    
    #错误处理
    try:
        #定义mail_client变量,开启SSL邮件,传入邮件服务器地址和端口
        mail_client = smtplib.SMTP_SSL(host,465)
        #连接smtp服务器
        mail_client.connect(host)
        #登录smtp服务器
        mail_client.login(user,passwd)
        #发送邮件
        mail_client.sendmail(sender,receiver,message.as_string())
        #发送完成关闭连接
        mail_client.close()
        print('邮件已成功发送给:'+receiver)
    except:
        #如果过程中抛出异常则提示用户
        print('发送失败!')

我们弄好了发送邮件的函数,可以单独把邮件内容拎出来测试下,在函数前面加几个用户输入动作:

title = input(str('输入邮件标题:'))
article = input(str('输入邮件内容:'))
receiver = input(str('输入接收人:'))

在函数下方加入:

send_mail(title,article,receiver)

这几个变量会自动传入send_mail()函数中。 保存为send_mail.py,运行一下:

image.png

成功接收到邮件:

 image.png

四、整合成一个完整的程序并优化

最后我们把整个程序整合一下,拼成一个完整的爬虫程序。(只写架构和优化部分)

#coding=utf-8
#author=xiaoweigod
import urllib.request
from bs4 import BeautifulSoup
import smtplib
from email.header import Header
from email.mime.text import MIMEText
import time

url_list=[' 
tmp = {'history':None}
receiver = input(str('请输入邮件接收人:'))

def get_webInfo(url)
   #加入UA,防止被网站封
   head = {}
   head['User-Agent']='Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19' 
   req = urllib.request.Request(url,header=head)
   
   ......
   
    return info_text+'\n'+url[:-50]+info_link+'\n'
    
def parseWeb(url_list):
    ......
    return result

def check():
    ......
    print('发现更新')
    #美化输出的格式
    result+='-----------------------------------------\n'
    result+=b
    result+='-----------------------------------------\n'
    ......
    print('更新内容如下:+result')
    #将result传入article变量,作为邮件正文
    send_mail('你关注的网站有更新',result,receiver)

#无限循环
while True:
    check()
    print('\n休息30秒继续运行!')
    time.sleep(30)
    print('继续工作...')

运行效果如下:

image.png

image.png

【超详细】使用python3做一个爬虫,监控网站信息上篇(获取网页信息)

misery阅读(7991)

由于朋友需要监视几个网页,来获取网页的更新信息。之前使用人工刷新的方法,不仅耗时耗力,效率低,而且时效性很差。于是委托我做一个程序,可以监控这几个网页的更新信息,如果页面更新了东西的话,可以直接通过邮件/微信发送给他。作为一个python还未入门的选手,对我而言这是个不小的挑战,首先感谢@wkm(博客:https://www.xiaoweigod.cn),node大佬的倾情帮助,对于程序的逻辑改进给出了惊为天人的方案。

这里我把这个python爬虫的写法详细地写成博文,以备日后参考,也方便给萌新们学习一下,也请大佬们高抬贵手。当然更希望大佬们可以提出一些指点意见,非常感谢!

首先介绍下这几个网站:

http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:项目公告 属性:全部 市场:全部

http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=list&cur=1  类型:销售 公告:中标公告 属性:全部 市场:全部

http://wz.cd-rail.com/mainPageNoticeList.do?method=list&cur=1  类型:销售 公告:项目公告 属性:全部 市场:全部

http://wz.cd-rail.com/mainPageNoticeList.do?method=list&cur=1  类型:销售 公告:中标公告 属性:全部 市场:全部

http://wz.xian.95306.cn/mainPageNoticeList.do?method=list&cur=1  类型:销售 公告:项目公告 属性:全部 市场:全部

http://wz.xian.95306.cn/mainPageNoticeList.do?method=list&cur=1  类型:销售 公告:中标公告 属性:全部 市场:全部

http://wz.huhht.95306.cn/mainPageNoticeList.do?method=list&cur=1  类型:销售 公告:项目公告 属性:全部 市场:全部

http://wz.huhht.95306.cn/mainPageNoticeList.do?method=list&cur=1  类型:销售 公告:中标公告 属性:全部 市场:全部

http://wz.qingz.95306.cn/mainPageNoticeList.do?method=list&cur=1  类型:销售 公告:项目公告 属性:全部 市场:全部

http://wz.qingz.95306.cn/mainPageNoticeList.do?method=list&cur=1  类型:销售 公告:中标公告 属性:全部 市场:全部

比如我们打开第一个链接, http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=list&cur=1  点击筛选条件,类型为销售,公告类型为项目公告,属性和市场选全部。如图,我们要获取的是这个页面的更新信息。

image.png

  

一、思路分析

1. 那么如何获取这个页面的更新信息呢?其实简单得来讲,只要循环对比栏目中的第一条信息,如果获取到的第一条信息与上一次获取到的第一条信息不一样,那么可以判定更新了,这个时候通过邮件,把更新的信息发送给用户就可以了。虽然这几个网站虽然地址不一样,但是网页结构是一模一样的,只要拿其中一个页面分析,直接套用到其他网页就好了。

image.png

 

画一下流程图:

2. 如何利用python3来实现

网页获取可以用python3的urllib模块,网页分析可以用BeautifulSoup模块,邮件发送可以用smtplib和mail模块。

通过urllib模块下载整个网页,然后使用BeautufulSoup分析并提取有用的信息,最后使用mail模块,利用smtp给用户发送更新邮件

 

二、分析web页面

在写爬虫前,必须对需要爬取的页面进行分析,找出需要爬取的信息,然后罗列出来,一一在程序里实现。下面以第一个任务(http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=list&cur=1  类型:销售 公告:项目公告 属性:全部 市场:全部)为例,介绍如何分析web页面。

1 分析页面的链接地址规则。

使用 chrome浏览器,打开这个页面,我们看到筛选条件是默认的:

image.png

然后我们依次点击 销售,项目公告

image.png

发现浏览器的地址根本就没有改变。那么如何确定在这个筛选条件下,这个页面的唯一性呢?

我们可以用抓包工具,抓取点下链接后浏览器向服务器的请求地址。chrome浏览器自带抓包工具,如图,按F12点击network

image.png

然后我们再依次点击 销售-项目公告

network里面更新了抓到的包,如图,我们查看第一个文件的header

image.png

这里清楚地显示了文件的请求地址是“http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1&keyword=&inforCode=&time0=&time1=”,我们新开一个窗口,把这个请求地址粘贴进去:

image.png

如上图,虽然连接还是跳转回到了原来的连接,但是下面的筛选条件却改变了,我们找到了这个筛选条件下页面的唯一地址。

再来分析一下这个唯一地址(http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1&keyword=&inforCode=&time0=&time1=),发现里面的keyword、inforCode、time、time这些参数都没有指定值,可以直接去掉,因此可以将这个链接改为:http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1 

粘贴到浏览器,看一下,没有问题。

2 分析需要采集的数据

如图,我们需要采集的是最新一条信息的链接标题和链接地址。在这个链接上右键-检查。可以看到完整的信息:

image.png

这是一个 a 标签,“href”后面包含的是链接的地址(mainPageNotice.do?method=info&id=OJ002018051602811725%40OJ002018051602811726%4030),其他则是文字标题([线下][竞卖]5月25日废旧物资网上竞价销售公告)。

 

三、用python3尝试采集这个链接

这里我用centos7,安装python3环境,安装方法请看:

centos下安装python3并与自带的python2共存

需要用到python3的urllib模块和BeautifulSoup模块。运行以下命令安装:

pip install BeautifulSoup4

urllib模块自带的,因此无无需安装。

1 用urllib获取页面

用vim新建一个名为 get_update.py的python程序

vim get_update.py

获取http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1这个页面的代码如下:

#coding=utf-8
#调用urllib.request模块
import urllib.request

#定义url地址
url='http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1 '
#定义发送的请求
req=urllib.request.Request(url)
#将服务器返回的页面放入rsp变量
rsp=urllib.request.urlopen(req)
#读取这个页面,并解码成utf-8格式,忽略错误,放入变量html中
html=rsp.read().decode('utf-8','ignore')
#打印html里的内容
print(html)

保存,然后 python get_update.py 运行一下,可以看到打印出了整个网页内容:

image.png

2 用BeautifulSoup分析采集页面

上一步已经获取到了页面的内容,这一步我们通过BeautifulSoup来分析页面,并提取信息。

beautifulSoup的官方文档:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html

通过官方文档的描述,我们可以使用BeautifulSoup里的find_all()函数,找出所有的a标签,然后用get()函数获取a标签的内容,使用get_text()函数获取文字。

继续添加如下代码:

#导入BeautifulSoup模块
from bs4 import BeautifulSoup

#使用BeautifulSoup模块解析变量中的web内容
html=BeautifulSoup(html,'html.parser')
#循环找出所有的a标签,并赋值给变量 link
for link in html.find_all('a'):
    #把href中的内容赋值给info_link
    info_link=link.get('href')
    #把a标签中的文字赋值给info_text,并去除空格
    info_text=link.get_text(strip=True)
    #打印出info_text和info_link,并换行
    print(info_text)
    print(info_link+'\n')

注意,python对代码的缩进有严格的要求,不能删掉上面的缩进

保存运行一下,可以看到,所有的链接地址和文字都打印出来了:

image.png

3 补全链接

我们看到采集的链接,似乎只有一半。这是因为在html中,约定点击这种链接,前面自动加上网站的地址。比如点击 mainPageNotice.do?method=info&id=IJ002018051501758115%40IJ002018051501758050%4030 浏览器会理解为点击:http://wz.lanzh.95306.cn/mainPageNotice.do?method=info&id=IJ002018051501758115%40IJ002018051501758050%4030

但是我们采集出来给用户的信息不能这样,看起来有点没头没脑的。怎么才能补全链接呢?如果直接在输出的链接前加上前缀字符串(网站域名) 'http://wz.lanzh.95306.cn/'  的话,那么其他不同域名网站就没法采集

那么怎么根据网站的域名变化定义这个前缀的字符串(网站域名)呢?我们可以看到,url变量里面就包含了这个前缀字符串,我们可以把url这个变量当成python的列表处理,提取其中的域名。

根据第二节的1小节,分析链接地址,我们把需要采集的页面,全都转化为get请求的地址,既:

http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=list&cur=1 类型:销售 公告:项目公告 属性:全部 市场:全部

http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=list&cur=1  类型:销售 公告:中标公告 属性:全部 市场:全部

http://wz.cd-rail.com/mainPageNoticeList.do?method=list&cur=1  类型:销售 公告:项目公告 属性:全部 市场:全部

http://wz.cd-rail.com/mainPageNoticeList.do?method=list&cur=1  类型:销售 公告:中标公告 属性:全部 市场:全部

http://wz.xian.95306.cn/mainPageNoticeList.do?method=list&cur=1  类型:销售 公告:项目公告 属性:全部 市场:全部

http://wz.xian.95306.cn/mainPageNoticeList.do?method=list&cur=1  类型:销售 公告:中标公告 属性:全部 市场:全部

http://wz.huhht.95306.cn/mainPageNoticeList.do?method=list&cur=1  类型:销售 公告:项目公告 属性:全部 市场:全部

http://wz.huhht.95306.cn/mainPageNoticeList.do?method=list&cur=1  类型:销售 公告:中标公告 属性:全部 市场:全部

http://wz.qingz.95306.cn/mainPageNoticeList.do?method=list&cur=1  类型:销售 公告:项目公告 属性:全部 市场:全部

http://wz.qingz.95306.cn/mainPageNoticeList.do?method=list&cur=1  类型:销售 公告:中标公告 属性:全部 市场:全部

转化为:

http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1

http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1

http://wz.cd-rail.com/mainPageNoticeList.do?method=init&id=2000001&cur=1

http://wz.cd-rail.com/mainPageNoticeList.do?method=init&id=2200001&cur=1

http://wz.xian.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1 

http://wz.xian.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1

http://wz.huhht.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1

http://wz.huhht.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1

http://wz.qingz.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1

http://wz.qingz.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1

观察一下这些转化后的地址,可以发现虽然域名一样,但域名后面的内容是一样的

我们直接在python里面看一下,域名后面字符的长度:

image.png

长度为50,那么如果把完整的地址(url变量)当成一个列表,从倒数第51个字符串开始,反向截取,看看是什么结果。

image.png

可以看到,用url[:-50]就可以返回该网站的域名,那么上面的代码打印输出那两行我们再修改下:

    print(info_text)
    print(url[:-50]+info_link+'\n')

image.png

保存运行,查看下结果:

image.png

成功加上了域名的前缀。

4.获取页面的第一条信息的地址和标题

上面我们已经可以采集这个页面所有的链接地址了。可是我们只需要第一条公告中的链接。BeautifulSoup模块中的find_all()函数提供了限制采集的功能,我们来尝试一下。

将上面的 for link in html.find_all('a'): 改为 for link in html.find_all('a',limit=5):

image.png

重新运行下查看结果:

image.png

打印出了前5个链接,我们需要的是第3个链接

把 limit的值改为3,再次运行:

image.png

怎么把前面2个链接去掉呢? 其实很简单,我们看这个for循环:

for link in html.find_all('a',limit=3):
    #把href中的内容赋值给info_link
    info_link=link.get('href')
    #把a标签中的文字赋值给info_text,并去除空格
    info_text=link.get_text(strip=True)
    #打印出info_text和info_link,并换行
    print(info_text)
    print(url[:-50]+info_link+'\n')

每找到一个a标签,就给info_link和info_text赋值一次,并打印出来。因为赋值是会覆盖的,所以可以等整个for循环运行完毕后,再把info_link和info_text的值打印出来,就对应了最后一条链接(第三条)的内容。

再次更改程序,把print这两条的缩进去掉,移除for循环:

for link in html.find_all('a',limit=3):
    #把href中的内容赋值给info_link
   info_link=link.get('href')
  #把a标签中的文字赋值给info_text,并去除空格
  info_text=link.get_text(strip=True)
#打印出info_text和info_link,并换行
print(info_text)
print(url[:-50]+info_link+'\n')

运行一下:

image.png

成功采集到这条地址。

四、批量采集所有页面中的第一个链接

如果要采集上面十个页面的各自第一个链接,需要定义一个url的合集(列表),把所有需要采集的页面地址都包含进去:

url_list=['http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1','http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1','http://wz.cd-rail.com/mainPageNoticeList.do?method=init&id=2000001&cur=1','http://wz.cd-rail.com/mainPageNoticeList.do?method=init&id=2200001&cur=1','http://wz.xian.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1','http://wz.xian.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1','http://wz.huhht.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1','http://wz.huhht.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1','http://wz.qingz.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1','http://wz.qingz.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1']

然后用for循环把url全都地址打印出来,再把url传入我们的程序里,最后再打印出来。大致拓扑如下:

for url in url_list:
    urllib访问获取页面
    ...
    for link in html.find_all('a',limit=3)
        BeautifulSoup处理提取a标签内容
        ...
    print(info_text)
    print(url[:-50]+info_link+'\n')

完整代码如下:

#coding=utf-8
#导入urllib.request模块
import urllib.request
#导入BeautifulSoup模块
from bs4 import BeautifulSoup

#把所有需要采集的页面地址都放入url_list这个列表
url_list=['http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1','http://wz.lanzh.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1','http://wz.cd-rail.com/mainPageNoticeList.do?method=init&id=2000001&cur=1','http://wz.cd-rail.com/mainPageNoticeList.do?method=init&id=2200001&cur=1','http://wz.xian.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1','http://wz.xian.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1','http://wz.huhht.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1','http://wz.huhht.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1','http://wz.qingz.95306.cn/mainPageNoticeList.do?method=init&id=2000001&cur=1','http://wz.qingz.95306.cn/mainPageNoticeList.do?method=init&id=2200001&cur=1']

#第一层循环,把url都导出来
for url in url_list:
    #定义发送的请求
    req=urllib.request.Request(url)
    #将服务器返回的页面放入rsp变量
    rsp=urllib.request.urlopen(req)
    #读取这个页面,并解码成utf-8格式,忽略错误,放入变量html中
    html=rsp.read().decode('utf-8','ignore')
    #使用BeautifulSoup模块解析变量中的web内容
    html=BeautifulSoup(html,'html.parser')
    #第二层循环,找出所有的a标签,并赋值给变量 link
    for link in html.find_all('a',limit=3):
        #把href中的内容赋值给info_link
        info_link=link.get('href')
        #把a标签中的文字赋值给info_text,并去除空格
        info_text=link.get_text(strip=True)
    #打印出info_text和info_link,并换行
    print(info_text)
    print(url[:-50]+info_link+'\n')

运行一下,结果如下:

image.png

不小心写多了……未完待续 请看下篇

【超详细】使用python3做一个爬虫,监控网站信息下篇(循环对比,发送邮件)

centos下安装python3并与自带的python2共存

misery阅读(2330)

由于自学python3,需要在服务器上调试python程序。在centos中,自带有python2,因此需要经常安装python3。但是这里有一个坑,就是centos的yum是用python2写的,如果正常编译安装python3,那么yum就会直接挂了。为了方便以后编译安装python3,不用天天去网上找教程,准备写下这篇文章,供日后参考。

首先连上服务器,看下python版本:

python -V

如图我们可以看到centos下,默认带有python2.7.5:

image.png

接下来我们开始安装python3。

 

1 安装python3所需要的组件

yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gcc make wget libffi-devel

安装完成如图:

image.png

 

2 备份现有的python2

cd /usr/bin
mv python python.bak
mv pip pip.bak

然后试下yum,发现已经挂了:

image.png

 

3 下载并编译安装python3

这篇文章我们选择最新的稳定版本3.6.5,如有更新可以自行去官网(https://www.python.org/downloads/source/)查找下载地址。

cd ~ && wget https://www.python.org/ftp/python/3.6.5/Python-3.6.5.tar.xz 
tar -xvJf Python-3.6.5.tar.xz
cd Python-3.6.5
#指定安装路径
./configure prefix=/usr/local/python3
#编译并安装
make && make install

编译安装完成后如图:

image.png

接下来做一条软连接,将python3指向python,顺便把pip也重新指向:

ln -s /usr/local/python3/bin/python3 /usr/bin/python
ln -s /usr/local/python3/bin/pip3 /usr/bin/pip

这时候我们执行一下python,看看版本回显信息:

python -V 
python2 -V

如图可以看到,python命令对应了python3.6.5,python2命令对应了python2.7.5。

image.png

 

4 修改yum配置

装完了python3之后,我们要修一下yum了,不然以后都没法装软件和环境了。

vim /usr/bin/yum

如图,按insert,把头部的 #! /usr/bin/python 修改为 #! /usr/bin/python2

image.png

修改完成后按esc,然后输入 :wq 保存退出

同样修改一下 urlgrabber-ext-down 文件,把头部的 #! /usr/bin/python 修改为 #! /usr/bin/python2

vim /usr/libexec/urlgrabber-ext-down

image.png

改完之后再试一下yum,已经恢复正常:

image.png

接下来就愉快地用python3写程序吧~

2017上海ChinaJoy美图分享

misery阅读(2326)

提示:去了趟ChinaJoy,多图慎入。全都是高清原图,请勿使用手机流量观看!请勿使用手机流量观看!请勿使用手机流量观看!