教你写个QQ机器人(4)让你的机器人学会看妹子

市场调研

炎炎夏日,唯有空调和西瓜(西瓜皮不知道是什么垃圾的请看上一节【JAVA】教你写个QQ机器人(3)让你的机器人学会垃圾分类),才能抵御住热浪的侵袭与蝉鸣的噪耳。此时此刻,如果再有一个姣好的妹子,那就十分快哉了。当然,妹子是不可能有的,但我们可以实现他。


功能需求

发送“看妹子,来n(n是具体数字,0<n<=30)张”,则机器人回复n张妹子图。听起来是不是很有意思,来让我们开始动手吧。


资源获取

花瓣网可是个好东西啊,里面的图的质量都非常的高。网站链接花瓣_美女模块

我们利用Jsoup直接抓取img标签就可以了,但实践下来发现,图片是js动态加载出来的,而且还有下拉加载。

先看一下妹子图片的url构成,例如

https://hbimg.huabanimg.com/22e0be71929e945750b27f617e07ae73fc51e5482ce6b8-J5YenR_fw658

多看几个图片的地址,我们就会发现,图片的地址由https://hbimg.huabanimg.com/加key构成,这个key怎么获取,我们继续探索。

首先利用Jsoup获取网页源码的body部分

        Element element = Jsoup.connect(URLConst.HUABAN_MZ_INDEX).get().body();
        System.out.println(element);

body部分是一个json数组,我们主要关心pins数组中的对象,包涵pin_id(下拉刷新需要用到),file.key(就是上述的key),file.type(图片类型)

app.page["board"] = {
   .....
   "pins": [
             {
               "pin_id": 2577135993,
                ....
                "file": {
                          ...
                          "key": "ebdf851738f3641ac99bad620e29216fcf4fa466afbc5-Rt8jP4",
                          "type": "image/jpeg",
                          ...
                        },
                        ...
             },
             {
               "pin_id": 2577134964,
                ....
                "file": {
                          ...
                          "key": "4b460d009b23dff2e59c0ca8965473245a98452d1f4837-OTHmRy",
                          "type": "image/jpeg",
                          ...
                        },
                        ...
             }...

现在我们需要做的,就是利用正则表达式,将属性设置进对象中

    @Test
    public void getMZ() throws IOException {
        Element element = Jsoup.connect(URLConst.HUABAN_MZ_INDEX).get().body();
        List<MzImg> mzImgList=parsePinsFromXml(element.toString());
        mzImgList.forEach(System.out::println);
    }

    private List<MzImg> parsePinsFromXml(String xmlStr) {
        List<MzImg> pins = new ArrayList<MzImg>();
        String pattern = "\\{\"pin_id\":(\\d+),.+?\"key\":\"(.+?)\",.\"type\":\"image/(.+?)\",";

        // 创建 Pattern 对象
        Pattern r = Pattern.compile(pattern);

        // 现在创建 matcher 对象
        Matcher m = r.matcher(xmlStr);
        while (m.find()) {
            MzImg pin = new MzImg();
            pin.setPinId(m.group(1));
            pin.setKey(m.group(2));
            pin.setType(m.group(3));
            pins.add(pin);
        }
        return pins;
    }

其中最后四个

MzImg{pinId='2518975235', key='adc818007febafb3031f0019d72426982bae22372d0e7-Aasf9n', type='jpeg'}
MzImg{pinId='2518975147', key='fedb28ec155d69be514d8a374519312e4573f4322a743-MeT2Gg', type='jpeg'}
MzImg{pinId='2518974230', key='8e211f60e459518a4165b3dfdc204f2822d121c8153dbe-hcCdUL', type='jpeg'}
MzImg{pinId='2518974067', key='24b6c089d69a8e547dbdd894c30826bbb20cb8324a483a-3iQC10', type='png'}

到这一步,我们已经能获取第1页的图片url,接下来我们处理下拉刷新

废话不多说,直接F12,观察下拉的url

Request URL: https://huaban.com/boards/481662/?jy55g2cp&max=2518974067&limit=20&wfl=1

结合遍历输出的最后四个的属性值,可以看得出max值为第一页中最后一个图片的pinId,limit为每页显示多少张图片,wfl为下拉前的页数。

至此,我们已经能抓取妹子图片了,下一步我们将之与QQ机器人结合起来。


大展身手的机器人

整体代码见我的GithubQQ机器人,目前拥有垃圾分类、看妹子(嘶~)、关键词监控功能

我们对机器人进行私聊,handleReceiveMsg依据消息种类转发给handlePrivateMsg方法

    /**
     * 由收到的消息类型转发给特定的方法
     *
     * @param receiveMsg
     */
    private ReplyMsg handleReceiveMsg(ReceiveMsg receiveMsg) {
        String post_type = receiveMsg.getPost_type();
        switch (post_type) {
            case "message":
                //收到消息
                String message_type = receiveMsg.getMessage_type();
                switch (message_type) {
                    case "private":
                        //私聊消息
                        return privateMsgService.handlePrivateMsg(receiveMsg);
                    ...
                }
                break;
                ...
        }
        return null;
    }

其中handlePrivateMsg方法又调用了sendMZPicByMsg方法

    public ReplyMsg handlePrivateMsg(ReceiveMsg receiveMsg) {
        Long user_id = receiveMsg.getUser_id();
        String raw_message = receiveMsg.getRaw_message();

        //查询指令是否标准,不标准则反馈提示
        if (MsgUtil.getMenu(raw_message) != null) {
            ReplyMsg replyMsg = new ReplyMsg();
            replyMsg.setReply(MsgUtil.getMenu(raw_message));
            return replyMsg;
        }

        //kmz
        if (raw_message.contains("看妹子")) {
            kmzService.sendMZPicByMsg(receiveMsg);
        }
        ...
        return null;
    }

KmzServiceImpl类的全部代码

package com.sun.kq.service.impl;

import com.sun.kq.constant.FileConst;
import com.sun.kq.constant.URLConst;
import com.sun.kq.enums.MessageType;
import com.sun.kq.model.*;
import com.sun.kq.service.GroupMsgService;
import com.sun.kq.service.KmzService;
import com.sun.kq.service.PrivateMsgService;
import com.sun.kq.util.ImageDownloadUtil;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
public class KmzServiceImpl implements KmzService {

    @Autowired
    KmzService kmzService;

    @Autowired
    GroupMsgService groupMsgService;

    @Autowired
    PrivateMsgService privateMsgService;

    @Override
    public ReplyMsg sendMZPicByMsg(ReceiveMsg receiveMsg) {
        String raw_message = receiveMsg.getRaw_message();
        int n = 1;
        try {
            n = Integer.parseInt(raw_message.split("来")[1].split("张")[0]);
        } catch (Exception e) {
        }
        if (n < 0 || n > 30) {
            ReplyMsg replyMsg = new ReplyMsg();
            replyMsg.setReply("求求你做个正常的人");
            replyMsg.setAt_sender(true);
            return replyMsg;
        }

        List<String> urlList = kmzService.getKmzImageKey(n);
        for (String key : urlList) {
            try {
                boolean exist = ImageDownloadUtil.isImgExist(key);
                if (!exist) {
                    ImageDownloadUtil.downloadImg(key);
                    System.out.println("图像不存在");
                } else {
                    System.out.println("图像存在");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            String message = "[CQ:image,file=" + FileConst.KMZ_IMG_PREFIX + key + ".jpg]";

            switch (MessageType.getEnumByType(receiveMsg.getMessage_type())) {
                case GROUP:
                    GroupMsg groupMsg = new GroupMsg();
                    groupMsg.setMessage(message);
                    groupMsg.setGroup_id(receiveMsg.getGroup_id());
                    groupMsgService.sendGroupMsg(groupMsg);
                    break;
                case PRIVATE:
                    PrivateMsg privateMsg = new PrivateMsg();
                    privateMsg.setMessage(message);
                    privateMsg.setUser_id(receiveMsg.getUser_id());
                    privateMsgService.sendPrivateMsg(privateMsg);
                    break;
            }
        }
        return null;
    }

    @Override
    public List<String> getKmzImageKey(int n) {
        List<String> keyList = new ArrayList<>();
        try {
            Element element = Jsoup.connect(URLConst.HUABAN_MZ_INDEX).get().body();
            List<MzImg> mzImgList = parsePinsFromXml(element.toString());
            String lastPid = mzImgList.get(mzImgList.size() - 1).getPinId();

            for (int page = 1; page <= n / 2 + 1; page++) {
                Element tempElement = Jsoup.connect("https://huaban.com/boards/481662/?jxt53umu&max=" + lastPid + "&limit=20&wfl=" + page).get().body();
                List<MzImg> tempUrlList = parsePinsFromXml(tempElement.toString());
                lastPid = tempUrlList.get(tempUrlList.size() - 1).getPinId();
                mzImgList.addAll(tempUrlList);
            }

            while (true) {
                int random = new Random().nextInt(mzImgList.size() - 1);
                String key = mzImgList.get(random).getKey();
                if (!keyList.contains(key)) {
                    keyList.add(key);
                }
                if (keyList.size() == n) {
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return keyList;
    }


    private List<MzImg> parsePinsFromXml(String xmlStr) {
        List<MzImg> pins = new ArrayList<MzImg>();
        String pattern = "\\{\"pin_id\":(\\d+),.+?\"key\":\"(.+?)\",.\"type\":\"image/(.+?)\",";

        // 创建 Pattern 对象
        Pattern r = Pattern.compile(pattern);

        // 现在创建 matcher 对象
        Matcher m = r.matcher(xmlStr);
        while (m.find()) {
            MzImg pin = new MzImg();
            pin.setPinId(m.group(1));
            pin.setKey(m.group(2));
            pin.setType(m.group(3));
            pins.add(pin);
            System.out.println(pin.getPinId() + "," + pin.getKey() + "," + pin.getType());
        }
        return pins;
    }

}

当用户发送“看妹子,来n张”时,我们就在1~n/2+1页数中随机抽取n张不同的图片, 并下载到酷Q安装目录下的data/image中,之后利用CQ码,CQ码中携带data/image中图片的地址,再将CQ码封装在PrivateMsg中,最后对发起用户执行n次的sendPrivateMsg操作。

到这里,所有的代码就编写完成了。


一睹芳容

ok,大工告成。

 

作为一个***丝程序员,每天在空闲的时间做一些有意思的项目,实属是最大的期待了。