通过django 对接微信公众号后台 ,顺便还在知乎上给咱社区打了个广告hh


从零开始写Python爬虫 --- 爬虫应用: requests+django实现微信公众号后台

呦呦呦,期末考试这就完结了,从明天开始就要进入忙碌的暑假了,这也会是我最后一个暑假了,要好好珍惜哈,最近因为考试(懒)一直都没有更新,可是我这不是来了嘛。

我们到现在也写了不少小爬虫了,可是说实在都是自娱自乐,也没有用到实际的项目中去,这次我们就来用爬充和Django实现微信公众号自动回复「段子」的功能。

具体效果:

目前功能还比较简陋:

打个小广告, 欢迎关注我的公众号:findyourownway 里面会分享一些和「技术相关」的文章~

爬虫的编写:

爬虫的部分十分简单,只是爬取糗百首页的段子, 一段几十行的脚本就能解决,我就不细说了。 要是看不太明白,可以回头看看我以前发的文章啦

import requests
from bs4 import BeautifulSoup


def get_html_text(url):
    try:
        r = requests.get(url, timeout=3)
        r.raise_for_status()
        r.encoding = r.apparent_encoding
        return r.text
    except:
        return 'something wrong'


def get_jokes(url):
    '''
    返回当前url页面的糗百的
    段子作者,主体,热评
    返回类型:列表
    '''
    joke_list = []

    html = get_html_text(url)
    soup = BeautifulSoup(html, 'lxml')

    articles = soup.find_all('div', class_='article block untagged mb15')

    for article in articles:
        body = article.find('span').text
        author = article.find('img')['alt']
        try:
            comment = article.find(
                'div', class_='main-text').contents[0].replace('\n', '')
        except:
            comment = '暂时没有热评'

        joke = '作者:{}\n{}\n\n热评{}'.format(author, body, comment)
        joke_list.append(joke)

    if len(joke_list)==0:
        joke_list.append('爬虫出现错误,快联系作者去更新吧!')

    return joke_list

Django和公众号对接:

虽然微信有自己的后台自动回复功能, 但是由于我要实现某些不可描述的功能, 就不能用他自带的功能了, 好在微信还是给我们提供了很方便的接口的 只需要将微信公众号的后台和我们自己的服务器对接来处理「消息」就成

这里我用Django作为后端框架

django 是Python上非常成熟的第三方web框架,有着十分强大的功能,与之类似的还有 flask、webpy等.....

微信有着自己详细的官方文档来介绍如何接入服务器, 地址:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319

我也就不一步一步的说了, 最主要说一下需要注意的坑:

在微信端配置好服务器之后, 微信会以GET的方式给服务器发一段验证消息 在服务器端我们需要将这串信息按照规则解码 并给微信端返回验证信息,如果对比结果一致 那么你的服务器就成功对接上了

在对接成功之后,微信后台所收到的信息, 都会以xml的格式给你的服务器转发过去, 你后端服务器所需要做的就是:

  • 接收这段xml
  • 处理这段xml
  • 返回处理后的xml

有一个非常坑的一点是, 如果你的服务不能返回符合「微信格式」的xml, 微信会尝试连续五次给你的服务器发送同样的消息 直到你返回正确的消息,或者return一个'success'字符串

项目的生成:

首先通过命令行生存我们的django 项目:

$ django-admin startproject wx_backends

# 接着生成我们的app
cd wx_backends
python manage.py startapp ss

# 看一下当前目录:
.
├── manage.py
├── ss
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py #视图文件
└── wx_backends
    ├── __init__.py
    ├── __pycache__
    │   ├── __init__.cpython-36.pyc
    │   └── settings.cpython-36.pyc
    ├── settings.py       # 项目配置文件
    ├── urls.py #网址路由文件
    └── wsgi.py

4 directories, 14 files

# 配置: settings.py,将app加入其中 
INSTALLED_APPS = [
    'SS',
]

# 配置路由: urls.py
from django.conf.urls import url
from . import views
app_name = 'SS'
urlpatterns = [
    url(r'^$', views.wechat, name='wechat'),
]

视图函数的编写:

from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from lxml import etree
from django.utils.encoding import smart_str
import hashlib
import time

from .handle import main_handle


# Create your views here.

# 公众号自定义的token 可以自己设置,是一个校对用的常量
TOKEN = '***************'

# csrf_exempt 标记是为了取消django自带的csrf标记
@csrf_exempt
def wechat(request):
    '''
    所有的消息都用进过这个函数进行验证处理
    微信验证的消息是以GET方式获得的
    平时的收发则以POST的方式
    '''

    if request.method == 'GET':
        # 我们来获取微信给我们发送的验证消息
        signature = request.GET.get('signature', None)
        timestamp = request.GET.get('timestamp', None)
        nonce = request.GET.get('nonce', None)
        echostr = request.GET.get('echostr', None)
        token = TOKEN

        # 按照微信的验证要求将token字段timestamp、nonce字段惊醒字典顺序排序
        # 将三个参数字符串拼接成一个字符串进行sha1加密
        # 获得加密后的字符串可与signature对比,标识该请求来源于微信
        tmp_list = [token, timestamp, nonce]
        tmp_list.sort()
        hashstr = "%s%s%s" % tuple(tmp_list)
        hashstr = hashlib.sha1(hashstr.encode('utf-8')).hexdigest()

        # 如果得出的结果和微信服务器发来的相同,则将echostr返回去
        # 就能成功对接了
        if hashstr == signature:
            return HttpResponse(echostr)
        else:
            return HttpResponse('wx_index')

    if request.method == 'POST':
        # 从微信服务器获得转发来的各种消息

        # 这里将获取到的非uicode字符转换为可以处理的字符编码
        data = smart_str(request.body)
        xml = etree.fromstring(data)

        # 在控制台输出一下挑调试信息
        print('**********收到的XML***********\n')
        print(data)

        # 调用我们的handle函数来处理xml
        response_xml = main_handle(xml)

        return HttpResponse(response_xml)

主处理逻辑的编写:

'''
处理自动回复逻辑
每个消息都经过main_handl函数进行处理
replay_rules 是外部资源文件。
用来存储一个符合自动回复规则的字典

'''

from django.template.loader import render_to_string
import time
import random
# 引入自动回复字典文件
from .replay_rules import rules
# 引入外部处理函数
from .ss_invite import get_invite_code
from .qiubai import get_jokes


nav_bar = '''公众号正在开发中...

回复「指南」
即可获得精品文章

回复「爬虫」
即可获得相关文章

回复「段子/来个段子」
即可获新鲜的段子
'''
# 用来计算时间间隔的常量,每次消息传递后更新
global last_time
last_time = 1


def main_handle(xml):
    global last_time
    # 找到传来的消息事件:
    # 如果普通用户发来短信,则event字段不会被捕捉
    try:
        event = xml.find('Event').text
    except:
        event = '木有事件发生'

    try:
        # 找到此次传送的消息信息的类型和内容
        msg_type = xml.find('MsgType').text
        msg_content = xml.find('Content').text
    except:
        msg_type = ''
        msg_content = ''

    # 后台打印一下日志
    print('**********收到的数据***********')
    print(msg_type, event, '\n文本内容:', msg_content)

    # 判断是否是新关注的用户
    if event == 'subscribe':
        text = '欢迎关注公众号,回复「指南」即可获得精品文章'
        return parser_text(xml, text)

    # 判断消息类型是否是文本
    # 目前只能自动回复文本类型的消息
    if msg_type == 'text':
        # 当收到的信息在处理规则之中时
        if msg_content in rules.keys():
            text = rules[msg_content]
            return parser_text(xml, text)
        # 针对邀请码特殊处理
        elif msg_content == '邀请码':
            text = get_invite_code()
            return parser_text(xml,text)
        # 针对段子特殊处理
        elif msg_content == '段子' or msg_content == '来个段子':
            jokes = get_jokes('https://www.qiushibaike.com/')
            text = jokes[random.randint(0,len(jokes))]
            return parser_text(xml,text)
        # 当不属于规则是,返回一个功能引导菜单
        else:
            # 获取消息传递的时间
            t = int(time.time()) - last_time
            last_time = (int(time.time()))
            print(last_time)
            # 当用户连续发信息的时候,我们不自动回复
            if t > 5:
                return parser_text(xml, text=nav_bar)
            else:
                return 'success'
    else:
        return 'success'

def parser_text(xml, text):
    '''
    处理微信发来的文本数据
    返回处理过的xml
    '''
    print(text)
    # 我们反转发件人和收件人的消息
    fromUser = xml.find('ToUserName').text
    toUser = xml.find('FromUserName').text
    # event事件是咩有msg id 的
    try:
        message_id = xml.find('MsgId').text
    except:
        message_id = ''
    # 我们来构造需要返回的时间戳
    nowtime = str(int(time.time()))

    context = {
        'FromUserName': fromUser,
        'ToUserName': toUser,
        'Content': text,
        'time': nowtime,
        'id': message_id,
    }
    # 我们来构造需要返回的xml
    respose_xml = render_to_string('SS/wx_text.xml', context=context)

    return respose_xml

返回的xml模板

微信对于返回的xml有着强制的格式要求: 所以我们需要构造一个类似的模板:

<xml>
    <ToUserName>
        <![CDATA[{{ ToUserName }}]]>
    </ToUserName>
    <FromUserName>
        <![CDATA[{{ FromUserName }}]]>
    </FromUserName>
    <CreateTime>{{ time }}</CreateTime>
    <MsgType>
        <![CDATA[text]]>
    </MsgType>
    <Content>
        <![CDATA[{{ Content }}]]>
    </Content>
    <MsgId>{{ id }}</MsgId>
</xml>

由于只是大概的介绍, 也不能一下吧django的知识都说出来, 大家有兴趣的可以自己去了解学习一下

写在最后:

其实这篇文章大多和爬虫也没太多的关系 硬要说只是我个人将爬虫技术用在了日常的生活中 说实在每当这个时候,才觉得非常开心 而不是为了学写爬虫,而写爬虫 总之大家也多多将自己学习到的技术运用在生活之中吧。

说起来,经过一段时间的迷茫和犹豫, 我最终决定还是走向「web后端开发」这一条道路 大部分原因是我被Django的魅力迷上了吧 如果大家想学习如何使用这一强大的web框架 这里有很好的教程哦:

追梦人物的博客:http://zmrenwu.com/ Pythonzh中文社区: http://pythonzh.cn/

每天的学习记录都会 同步更新到: 微信公众号: findyourownway
知乎专栏:https://zhuanlan.zhihu.com/Ehco-python
blog : www.ehcoblog.ml
Github: https://github.com/Ehco1996/Python-crawler

  • 3 条回复 | 3 人参与
  • 额,感觉在这里显示的效果不太好,
    代码高亮和markdown的语法都有点小问题

    比如

    「>」

    引用部分

  • very nice! Bootstrap 的一些组件排版有点问题,过段时间会专门优化一下排版。

  • 滋瓷

添加一条新回复
登录 或者 注册 后发表回复