精华内容
下载资源
问答
  • 哪里可以做可以评论的网站
    千次阅读
    2020-11-11 14:39:54

    1、网络爬虫都能干什么

    @冰蓝

    之前在北京买房,谁想房价开始疯长,链家的房价等数据分析只给了一小部分,远远不能满足自己的需求。于是晚上花了几个小时的时间写了个爬虫,爬下了北京所有的小区信息及北京所有小区的所有历史成交记录。

    @陈乐群

    上次发现Android QQ和iOS QQ可以显示网络状态(2G/WiFi)之后,突然想到,这样子好像可以监视某人的出行和作息规律。简单的来说,在家里或者工作的地方,一般是有WiFi的,然后出门了,WiFi就断掉了。如果监测频率足够频繁,那么结合一定的推理,可以大致推测出一个人的行动。如果长期监视,那么可以大致推出一个人的作息时间。

    因为只有Android QQ和iOS QQ有这个功能,所以要得到一个人的网络状态比较麻烦。我的做法是跑 Android 模拟器。然后用按键精灵模拟,并把网络状态截图,用 curl post到服务器上。服务器会把每次发送的时间、截图保存下来。因为是用程序截图的,所以只要网络状态是一样的,那么截图就是一样的,这样服务器就只会保存2~3张图片而已,其余的发现是相同的图片,数据库做个标记就好了。然后人工做OCR,还是注意到只有2~3张图片,所以工作量很少。

    得到数据后,要做各种统计就可以自己搞了……

    @森羴

    在用Python写网页爬虫之前,我只用来写过了一个驾校约车的脚本,让当时的我不惧上万的学车同僚,在约车环节没有输在起跑线上。

    接着那段时间,我女朋友的领导每天下班都会下任务,要收集100条有招聘需求的信息,第二天检查。看到她熬夜百度+复制粘贴到半夜,心疼死了。

    想到了某个牛人说:一切重复性的工作都可以用程序来完成。于是偷偷花了些时间研究了下她经常查的某些同类业务网站的页面数据,培育了这只爬虫。主要技能就是爬这些网站的招聘公司信息及联系方式,保存到Excel中。

    在我将战斗成果----1000多个客户资料的Excel表格发给她的时候,先惊喜,后审问,再感慨!依稀记得那天她发了一条朋友圈,内容是:“有个程序员男朋友,感觉好幸福啊!!”成就感走直线啊,都能让她感到幸福,你说这只爬虫是不是做了很酷很有趣的事情呢?

    @柳易寒

    我用爬虫爬了我爱白菜网、超值分享汇、发现值得买、惠惠购物、今日聚超值、留住你、买手党、没得比、慢慢买、牛杂网、买个便宜货、什么值得买、天上掉馅饼、一分网、折800值得买、值值值等网站的折扣信息。

    这些网站都是提供的一些及时的、性价比较高的商品,很多时候要一个一个网站的看(重度用户),很容易就会错过一些很划算的商品。

    @小白

    大二学生一枚,前段时间中期考试,成绩一直不出来,又不想每次都登录,突然就像用以下所学的东西来干点事情。

    说干就干,花了我将近4个小时完成成绩提醒功能。主要是用Python定时抓取数据(定时用Ubuntu的crontab),分析数据是否变化,然后发送短信。其实大部分时间是花在分析学校模拟登陆那一块了,毕竟要提取各种值,还有url重定向,本来就才学Python,对一些东西也不是很熟悉。

    运行起来之后还是效果还不错,10分钟抓一次,第一时间知道了我的概率论。。。

    @顾旻玮

    在学校的时候做过一个项目,通过爬微博的文字,分析国内各个地区的用户收听虾米的热度和最受欢迎的歌手。当然也没有用什么很复杂的技术,就是写基本的TF-IDF。

    做完的时候觉得自己好有想法啊,能实现这么有意思的东西。后来发现早就有公司做过了。当然别人做的是美国版的。

    于是现在,我就在这家公司工作。

    @晨晨

    朋友交易了一套房子,手机号流落到了各种中介手里,隔几天就有中介电话骚扰,不胜其烦。每接一个电话都加黑名单,但还是有新号码打过来,so⋯⋯问我咋办!

    Android 手机的拦截倒不是问题,但需要房产经纪人的号码数据库,就只能去网上爬了!

    各个房产站的广州站点加上58什么的,一个多小时爬了快两万个号码,去重之后还有一万五千多⋯⋯

    一时兴起,又去爬了深圳、北京和上海,现在都不知道拿这些号码去干嘛了⋯⋯

    PS:貌似活跃房产经纪的数量能反应市场活跃度?

    PS:我觉得我可以把全国城市的都爬下来。

    @孟德超

    非计算机系。所以我做的比起其他人来说要简单的多,但是却解决了一些很实用的问题,也让我认识到各行各业的人都需要学一点编程。

    我一个同学做数学建模,需要57个城市两两之间的距离。他们本来想在百度查,可是57*56/2=1596,也就是说他们光查数据就要百度1596次。刚好我那个时候接触了一点爬虫,就找到一个可以查询距离的网站,大概写了几十行代码,两分钟就解决问题了。

    @余生梦

    说个简单实用的例子吧。昨晚突然发现我在某培训网站的的会员马上就要过期了,于是赶紧写了个爬虫,把没看完的教学视频全下载下来了……

    @ animalize

    用爬虫技术做了个个人信息收集系统,部署在卡片式电脑(如树莓派、Cubieboard)上。

    2、有哪些网站值得用python爬虫获取很有价值的数据

    0、IT桔子和36Kr在专栏文章中(http://zhuanlan.zhihu.com/p/20714713),抓取IT橘子和36Kr的各公司的投融资数据,试图分析中国各家基金之间的互动关系。

    1、知乎沧海横流,看行业起伏,抓取并汇总所有的答案,方便大家阅读,找出2015年最热门和最衰落的行业。

    2、汽车之家大数据画像:宝马车主究竟有多任性?利用论坛发言的抓取以及NLP,对各种车型的车主做画像。

    3、天猫、京东、淘宝等电商网站超越咨询顾问的算力,在用户理解和维护,抓取各大电商的评论及销量数据,对各种商品(颗粒度可到款式)沿时间序列的销量以及用户的消费场景进行分析。甚至还可以根据用户评价做情感分析,实时监控产品在消费者心目中的形象,对新发布的产品及时监控,以便调整策略。

    4、58同城的房产、安居客、Q房网、搜房等房产网站下半年深圳房价将如何发展 ,抓取房产买卖及租售信息,对热热闹闹的房价问题进行分析。

    5、大众点评、美团网等餐饮及消费类网站黄焖鸡米饭是怎么火起来的?抓取各种店面的开业情况以及用户消费和评价,了解周边变化的口味,所谓是“舌尖上的爬虫”。以及各种变化的口味,比如:啤酒在衰退,重庆小面在崛起。

    6、58同城等分类信息网站花10万买贡茶配方,贵不贵?抓取招商加盟的数据,对定价进行分析,帮助网友解惑。

    7、拉勾网、中华英才网等招聘网站互联网行业哪个职位比较有前途?抓取各类职位信息,分析最热门的职位以及薪水。

    8、挂号网等医疗信息网站如何评价挂号网? 抓取医生信息并于宏观情况进行交叉对比。

    9、应用宝等App市场你用 Python 做过什么有趣的数据挖掘/分析项目? 对各个App的发展情况进行跟踪及预测。(顺便吹一下牛,我们这个榜单很早就发现小红书App的快速增长趋势以及在年轻人中的极佳口碑)

    10、携程、去哪儿及12306等交通出行类网站,对航班及高铁等信息进行抓取,能从一个侧面反映经济是否正在走入下行通道。

    11、雪球等财经类网站抓取雪球KOL或者高回报用户的行为,找出推荐股票

    12、58同城二手车、易车等汽车类网站一年当中买车的最佳时间为何时?什么品牌或者型号的二手车残值高?更保值?反之,什么类型的贬值较快? - 二手车,找出最佳的买车时间以及最保值的汽车。

    13、神州租车、一嗨租车等租车类网站抓取它们列举出来的租车信息,长期跟踪租车价格及数量等信息

    14、各类信托网站通过抓取信托的数据,了解信托项目的类型及规模

    参考资料:

    【1】https://www.cnblogs.com/developer-qin/p/9661591.html  网络爬虫都能干什么?

    【2】https://blog.csdn.net/qiangw09/article/details/82969014    有哪些网站值得用python爬虫获取很有价值的数据

    更多相关内容
  • 本篇文章主要介绍了Android模拟登录评论CSDN实现代码,可以实现登陆发表评论到官方网站,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
  • 目前不会公布源码(因为有些安全问题与一些小bug),这篇文章主要如果你想体验这个评论可以返回主页看公告栏。 问题 一张表实现子评论评论 假设: A == 父评论 B == 子评论 C == 父评论 D == 子评论 问题1:如何判断...

    此评论样式采用valine的样式,JavaScript由博主个人独立编写

    目前不会公布源码(因为有些安全问题与一些小bug),这篇文章主要如果你想体验这个评论可以返回主页看公告栏。

    问题

    一张表实现评论评论
    假设:
    A == 父评论
    B == 子评论
    C == 父评论
    D == 子评论
    问题1:如何判断是子评论还是父评论
    问题2:如何判断B评论是A的子评论
    问题3:如何判断B评论不是C的子评论(如何区分开来)
    问题4:如何判断回复评论时,回复的是子评论还是父评论
    (如:如果我回复的是A评论,那么我(我是D)的这条评论就是A的子评论,要是我回复了B,如何将我(我是D)显示在在A评论下)
    还有几个小问题就不列举了

    正文

    Maven依赖

    <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.4</version>
            </dependency>
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    

    创建数据表

    数据库表结构

    CREATE TABLE `comment` (
      `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
      `artitle` BIGINT(20) NOT NULL DEFAULT '0' COMMENT '关联的blog主键', # 根据需求使用,去过你只是单纯的做个人主页留言的话,每个人评论全部设置为1即可,当然你也可以直接删除
      `nick` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '昵称',
      `mail` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '邮箱',
      `link` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '网址',
      `content` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '内容',
      `commentatorIP` VARCHAR(20) NOT NULL DEFAULT '' COMMENT 'ip地址',
      `createTime` DATETIME DEFAULT NULL COMMENT '评论时间',
      `isReply` INT(4) BIGINT '0' COMMENT '是否是回复 0为否 1为是',
      `commentStatus` BIGINT(4) NOT NULL DEFAULT '0' COMMENT '是否审核通过 0-未审核 1-审核通过',
      `commentUrl` VARCHAR(100) NOT NULL COMMENT '评论对应的页面地址',
      PRIMARY KEY (`id`)
    ) ENGINE=INNODB AUTO_INCREMENT=53 DEFAULT CHARSET=utf8;
    
    

    关键代码

    实体类、Service接口、ApiUtil类(调用QQ头像、Gravatar、QQ昵称等api)、MD5Util类(MD5加密)、PatternUtil类(正则表达式类,判断邮箱、网站地址等)js,css等
    以上代码将忽略 CommentMapper.xml也将忽略,本文只围绕文章开头的几个问题进行处理,并不展示回复等功能(就insert操作很简单)

    project1
    project2

    CommentMapper.java(接口)

    /**
     * @author Lete乐特
     * @createDate 2020- 12-25 19:17
     */
    @Mapper
    public interface CommentMapper {
    
        // 查询总数
        int count(); // 上面说了,只展示评论
    
        // 回复
        int reply(Comment comment);  // 上面说了,只展示评论,只需下面两个方法即可
    
        // 查询父评论
        @Select("select * from comment where isReply=0 AND commentStatus=1 ORDER BY createTime DESC")
        List<Comment> CommentListf();
        // 查询子评论
        @Select("select * from comment where isReply!=0 AND commentStatus=1 ORDER BY createTime ")
        List<Comment> CommentListz();
    }
    

    CommentServiceImpl.java(实现类)

    /**
     * @author Lete乐特
     * @createDate 2020- 12-25 19:22
     */
    @Service
    public class CommentServiceImpl implements CommentService {
    
        @Autowired(required=false)
        CommentMapper mapper;
    
        @Override // 查询总记录数
        public int count() {
            return mapper.count();
        }
    
        @Override // 回复
        public int reply(Comment comment) {
            // 判断是否是父评论
            if(comment.getIsReply()==null)comment.setIsReply(0);
            if(comment.getId()!=0)comment.setIsReply(comment.getId());
            comment.setCreateTime(new Date());
            comment.setCommentStatus(0);
            return mapper.reply(comment);
        }
    
        //-------------------------上方两个功能请忽略(虽然展示此代码无意义)-----------------------------------
    
        @Override // 查询父评论
        public List<Comment> CommentListf() {
            return mapper.CommentListf();
        }
    
        @Override // 查询子评论
        public List<Comment> CommentListz() {
            return mapper.CommentListz();
        }
    }
    
    

    ControllerComment.java(Controller层)

    /**
     * @author Lete乐特
     * @createDate 2020- 12-25 19:36
     */
    @Controller
    public class ControllerComment {
    
        @Autowired(required = false)
        private CommentServiceImpl commentService;
    
        // 获取Mail 根据Mail获取评论头像
        private List<Comment> Mail(List<Comment> list) throws IOException, ParseException {
            for (Comment comment: list){
                // 判断Mail   true: QQavatar  false: Gravatar
                if(PatternUtil.isQQEmail(comment.getMail())){ //PatternUtil之前描述了(正则表达式类,判断是否是qq邮箱)
                    // 获取@之前的内容(获取QQ号)
                    Integer qq = Integer.valueOf(comment.getMail().substring(0, comment.getMail().indexOf("@")));
                    // 得到qq号后将qq放到ApiUtil类里(获取qq头像并重新set)
                    comment.setMail(ApiUtil.getHeadImage(qq));
                }else{
                    // 不是qq邮箱则获取Gravatar头像
                    comment.setMail(ApiUtil.getGravatar(MD5Util.MD5Encode(comment.getMail())));
                }
            }
            // 最后返回
            return list;
        }
    
        @RequestMapping("/")
        public String comment(Model mod) throws IOException, ParseException {
            // 获取评论总数(只统计父评论)
            mod.addAttribute("count",commentService.count());
            // 获取父评论
            List<Comment> listf = commentService.CommentListf();
            mod.addAttribute("commentsf",Mail(listf)); // 查询信息,根据邮箱进行获取头像(传入上方的Mail方法)
            // 获取子评论
            List<Comment> listz = commentService.CommentListz();
            mod.addAttribute("commentsz",Mail(listz));// 查询信息,根据邮箱进行获取头像(传入上方的Mail方法)
            return "index";
        }
    }
    
    

    最后在template下新建index.html
    index.html

    <!DOCTYPE html>
    <html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <script src="https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js"></script>
    <div id="post-comment">
        <div class="comment-head">
            <div class="comment-headline">
                <i class="fas fa-comments fa-fw"></i>
                <span> 评论</span>
            </div>
        </div>
        <div class="comment-wrap">
            <div>
                <div class="vcomment v" id="vcomment">
                    <div class="vpanel">
                    </div>
                    <div class="vcount" style="display: block; color: #e58a8a;">
                        <span class="vnum" th:text="${count}"></span> 评论</div>
                    <div class="vcards">
                    <!-- 循环each父评论 -->
                        <th:block th:each="listf:${commentsf}">
                            <div class="vcard">
                                <!--判断是否是博主邮箱头像地址true==显示博主头像false==使用api转换头像  昵称、子评论、子评论一致-->
                                <img class="vimg lazyload"
                                     th:src="${listf.getMail()}"
                                     referrerpolicy="no-referrer">
                                <div class="vh">
                                    <div class="vhead">
                                        <a class="vnick" rel="nofollow" th:href="${listf.getLink()}" target="_blank" th:text="${listf.getNick()}"></a>
                                    </div>
                                    <div class="vmeta">
                                        <span style="color: #828282;" class="vtime" th:text="${#dates.format(listf.getCreateTime(),'yyyy-MM-dd HH:mm')}"></span>
                                        <span class="vat" th:value="${listf.getId()}">回复</span>
                                    </div>
                                    <div class="vcontent" data-expand="查看更多..." th:utext="${listf.getContent()}">
                                    </div>
                                    <div class="vreply-wrapper"></div>
                                    <!--循环each子评论-->
                                    <th:block th:each="listz:${commentsz}">
                                    <!--判断子回复是否与父id一致 在此出区分父评论,在评论-->
                                        <div class="vquote" th:if="${listz.getIsReply()}==${listf.getId()}" >
                                            <div class="vcard">
                                                <img class="vimg lazyload"
                                                     th:src="${listz.getMail()}"
                                                     referrerpolicy="no-referrer">
                                                <div class="vh">
                                                    <div class="vhead">
                                                       <a class="vnick" th:href="${listz.getLink()}" th:text="${listz.getNick()}"></a>
    
                                                    </div>
                                                    <div class="vmeta">
                                                        <span style="color: red;" class="vtime"th:text="${#dates.format(listz.getCreateTime(),'yyyy-MM-dd HH:mm')}"></span>
                                                        <span class="vat" th:value="${listf.getId()}">回复</span>
                                                    </div>
                                                    <div class="vcontent" data-expand="查看更多..." th:utext="${listz.getContent()}"></div>
                                                    <div class="vreply-wrapper"></div>
                                                </div>
                                            </div>
                                        </div>
                                    </th:block>
                                </div>
                            </div>
                        </th:block>
                    </div>
                    <div class="vload-top text-center" style="display:none;">
                        <i class="vspinner" style="width:30px;height:30px;"></i>
                    </div>
                    <div class="vcards"></div>
                    <div class="vload-bottom text-center" style="display:none;">
                        <i class="vspinner" style="width:30px;height:30px;"></i>
                    </div>
                    <div class="vempty" style="display: block;">
                        来发评论吧~
                    </div>
                    <div class="vpage txt-center" style="display:none">
                        <button type="button" class="vmore vbtn">加载更多...</button>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <link rel="stylesheet" href="/font/comment.css">
    <link rel="stylesheet" href="/css/Comment.css">
    <script src="/js/article.js"></script>
    </body>
    </html>
    

    运行后的效果图
    Comment

    看不懂?很迷?
    非常抱歉,由于目前部分功能为完善(其实我觉得完善了,但总感觉哪里不对劲,手动捂嘴笑),待处理完毕会发布到Github上

    展开全文
  • python爬取B站动态的评论总数(不含用户评论内容详情),想看接口分析和代码的,可跳过前言。更新,最核心的代码已删除,思路和其他代码保留。

    目录

    前言

    需求

    方案分析

    方案一

     方案二

    接口分析

    请求流程

    抓包演示

     请求接口

    接口说明

    接口测试

     代码


    前言

    想看接口分析和代码的,可跳过前言。

    更新,最核心的代码已删除,思路和其他代码保留。

    需求

    我参加了一些up主的动态抽奖,转赞评那种的,如图1。

    图1 - 动态抽奖

    开奖周期一般在两周到四周,如果粉丝数多的话,会被大量评论,而且他就没及时开过奖(指说几号开奖就几号开奖),实际都是比说的日期再晚几天才开奖。

    开奖后我想删除我的转赞评。转发在自己动态里就能删,点赞只要在up主原动态再点一下点赞按钮就能取消,评论却只能翻到自己的那条评论,才能删除。

    问了B站客服,他说只有以下方法能删除自己的评论:

    1. 手动翻到自己的那条评论
    2. 别人回复你的评论,你收到提醒,在提醒里点击对方回复的内容,可跳转到你的评论 

    方案分析

    按热度排序,我不确定我到底在哪,反正不在靠前的位置,应该是中间或后面。

    按时间排序,B站是把距离当前时间最近的评论,放在第一个(置顶除外),把从时间上真正第一个发的评论,放在最后一个。我认为这是降序,可我参加时一般都是up主发动态的当天,降序的话几乎得翻完全部评论才能找到我的评论,也就是说升序可以很快地找到我的评论,但B站不支持,我反馈了,客服说会给研发提。

    由于参与评论的人太多,手动翻是翻不到的;可是也没人回复我的评论,那怎么办?

    方案一

    首先我想到让程序自动翻页,我知道的有两类:

    • 按键精灵之类的软件,录制好键鼠动作,然后自动执行动作
    • 用代码操作浏览器,实现自动翻页

    第一类

    我用的不是按键精灵,是别人自己写的软件,功能类似。一开始翻页时,新数据加载得挺快;随着时间的推移,新数据加载得越来越慢。例如最开始时,只要页面滚动的位置到了,下一页的数据马上就能加载出来;一段时间后,虽然页面滚动的位置到了,也出现了正在加载的提示,但需要几秒甚至十几秒甚至几分钟才能显示出下一页的数据,这个加载时间越来越长。

    时间长点没事,只要能加载出来,我就能点删除。按热度排序的话,我得隔一会儿就搜下有没有加载出我的评论,且翻页次数非常非常多后,页面搜索很慢(我试了),也没搜到我。所以后来我又选了按时间排序,花了好几个小时,当加载到我发评论的前一天时,页面崩溃了,只能刷新,刷新后之前已加载的那么多评论全没了,又试了次依然这样。

    针对以上加载速度慢、页面崩溃,我能想到的原因有:

    • B站限制了数据返回的速率或频率
    • 我的网络原因(这个可能性最低)
    • 浏览器性能不够,我用的Edge
    • 电脑硬件性能瓶颈,如CPU和内存,我是I7-6700HQ,12G内存,三星860EVO

    实际是什么原因就不知道了,总之这个方法不行。

    第二类

    一开始,我用selenium打开那条动态,让selenium执行js代码,使页面滚动,但实际滚动的效果和手动用鼠标滚动不一样,好像是滚动几次就不行了,就加载不出新数据了,我忘了,总之这个方法不行。

    后来,我在浏览器Console里手动执行同样的js代码,效果和selenium一样,可能是代码原因,有知道的可以告诉我,或者其他写法也行。我试了以下几种,当然不是一起运行,一次运行一种:

    //第一种
    document.body.scrollTop=10000 // PhantomJS使用,headless不支持
    
    //第二种
    document.documentElement.scrollTop=10000 // 页面向下滚动指定的像素,数值很大时肯定能滚动到底部。这是页面滚动,不是模拟鼠标滚轮滚动
    
    //第三种
    var q=document.documentElement.scrollTop=10000 // 或这种写法 from https://www.cnblogs.com/landhu/p/5761794.html
    
    //第四种
    window.scrollTo(0,document.body.scrollHeight) // 滚动条回到底部。连续滚动时,有时上面两种js代码滚动一会儿就失效了,可以用这种 from https://www.cnblogs.com/xyztank/articles/14259330.html
    // window.scrollTo(0,0) // 滚动条回到顶部

     方案二

    方案一中的方法都不行,我不准备删了,突然有人回复我在某视频下的评论,如图2。

    图2 - 别人回复我的评论

      以往回复我的内容,字数很少,直接在图2中的界面就能看完,我就没点过。直到这次的太长了显示不完,我就点击了他回复的内容,然后竟然直接跳转到我的评论了,如图3。

    图3 - 点击图2后跳转到我的评论

     这正好符合客服说的第二种,并且向下翻,还看到了页码,如图4,不过这张图是后来测试时在某个动态的截图(因为我要删对动态的评论),但页码、跳转到指定页等内容都和当时视频那里一样,注意页面滚动条位于底部,平时B站无论动态还是视频,评论都是动态加载的,滚动条不会一直在底部,会触发加载下一页,除非所有评论真的记载完了,那样页面底部也有提示到底了。

    图4 - 图3页面的底部出现页码

     既然有页码了,那不是可以直接跳转到靠后的页面(按时间排序,我评论得早,一般都是up主发动态的当天),然后快速找到我的评论?最多就是多翻几页。可我点击页码后,页面内容还是当前页的内容,不能通过点击页码跳转页面,后面那个跳转到指定页也不管用。

    然后我看了当前URL,reply后应该是当前评论的ID,即我的评论的ID。

    https://www.bilibili.com/video/BV1aQ4xxxxxx#reply5816xxxxxx

    于是有了新思路,找到所有我参加过评论的动态,用爬虫爬取它们的所有评论数据,里面就有我的评论的ID,然后将ID拼接到动态的URL的末尾,访问就能跳转到我的评论,然后就能删除。如图5,当然那条不是我发的,我为了验证这个思路随意找了一条测试的。

    图5 - 访问构造的URL跳转到我的评论

    另外,因为是转赞评,有些抽奖动态我以前直接在我的动态里删除了转发 ,现在怎么找到它们从而删除评论?在up主的动态列表里,只要点赞按钮是蓝色,说明自己已点赞,已参加转赞评,如图6。但是转发和评论不会变蓝,无论自己有没有真的转发和评论。

    图6 - 点赞变蓝是已点赞,说明已参加转赞评

    点开这条动态,也能看到,如图7。

    图7 - 点开图6的动态

    接口分析

    请求流程

    1. 打开一条动态,如https://t.bilibili.com/56764707xxxxxxxxxx,注意已手动去除末尾的?tab=2
    2. 获取动态本身的信息,如类型、oid等
    3. 根据动态的类型、oid获取评论数据,当然必需的参数还有页码、排序方式

    抓包演示

    图8 - 步骤2的请求的参数

    图9 - 步骤2的请求返回的json数据,得到type

    图10 - 步骤3的请求返回的json数据,包含评论

     请求接口

    接口说明

    接口1,步骤2请求的接口

    https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/get_dynamic_detail?dynamic_id=

    参数dynamic_id就是步骤1的URL末尾的一长串字符,我称为动态ID。

    返回的json数据中有一个键是type,如图9,表示动态类型,它的值有以下几种可能:

    • 1:有转发内容
    • 2:动态有图片
    • 4:纯文字
    • 2048:可能是内容有站内分享(比如装扮推荐)
    • 其他情况有知道的可以补充

    接口2,步骤3请求的接口

    图10中携带了很多参数,但callback和plat和_都不是必需的,带上了反而可能出错(接口测试部分第三步会说),有人说callback和_是根据某种规则生成的,用于跟踪用户,后来我发现_的值很像当前时间的时间戳,callback的值的后半部分也很像当前时间的时间戳,且比_的值稍微小一点点。

    https://api.bilibili.com/x/v2/reply/main?jsonp=jsonp&next=&type=&oid=&mode=
    接口2的请求参数的说明
    参数名参数类型含义
    next字符串0和1都表示第一页,2表示第二页,3表示第三页...
    type字符串

    若接口1返回的type为2,则接口2的参数type的值为11

    若接口1返回的type为1、4、2048,则接口2的参数type的值为17
    oid字符串

    若接口1返回的type为2,则oid为图9或图12中返回值中的rid的值

    若接口1返回的type为1、4、2048,则oid的值和动态ID一样,即dynamic_id
    其他情况有知道的可以补充
    mode字符串

    3:按热度排序

    2:按时间排序

    接口测试

    为了确定抓包得到的URL、参数、响应之中,哪些参数是必需的,就得进行验证,在真正用代码验证之前,可以先用浏览器或postman测试,我以浏览器为例。

    根据请求流程中的三步,这里我省略第一步

    第二步,chrome直接访问构造好的带参数的URL(即接口1),成功返回json数据,如图11。另外,火狐可以直接格式化返回的json数据,如图12,这样就不用拿着chrome显示的json数据再去格式化网站上手动格式化,我之前用的格式化网站是JSON在线校验格式化工具(Be JSON),这是新版界面,旧版是在线JSON校验格式化工具(Be JSON),也可以在旧版界面点击新版跳转到新版,如图13。

    图11 - chrome访问接口1返回的json数据
    图12 - 火狐访问接口1返回的json数据
    图13 - 在旧版界面点击新版跳转到新版,也可以直接访问新版URL

     第三步,火狐直接访问构造好的带参数的URL(即接口2),成功返回json数据,如图14。另外,接口说明部分接口2处说带上callback和plat和_参数可能出错,如图15。

    图14 - 火狐访问接口2返回的json数据
    图15 - 火狐访问带上非必需的参数的URL时无法得到json数据

     修改接口2中参数的值,如表示页码的next,同样可以得到json数据,这里就不截图了。

    测试完成,说明接口URL及参数都是正确的,都能得到正确的json数据。

     代码

    更新,最核心的代码已删除,思路和其他代码保留。

    整体流程就是请求流程部分中的那三步,最终你想爬取什么数据,去接口2返回的json数据中提取就行。页码可以根据for循环生成,即for循环中先生成一个页码,再构造URL,再请求并获取响应数据,再从响应数据中提取需要的数据。

    另外,看完代码你可能有疑问,接口2返回的json数据中,已有all_count、is_end字段,如图14,为什么不直接用all_count除以每页的数据量得到页码总数进而得到最后一页的页码,或根据is_end判断是否是最后一页?

    针对第一种,我测试时按每页20条数据算的,得到的页码总数与图4中那个样子不一致,而且后来发现有的动态请求一页得到18条数据,总之这种方法不行,起码我测试时不行,也可能我哪里做错了。

    针对第二种,我测试时发现即便is_end为true,但依然可以请求下一页,只不过下一页得到的评论数据和本页相同;不过写文章时发现是测试时看错地方了,应该看与is_end的上一级cursor同级的replies,但是我代码里判断本页有没有数据确实是判断的这里的replies是否为空(在has_comment_data()中),所以也可以改成直接判断is_end是否为true,这种方法我写在注释里了 。

    已知问题:对同一动态爬取多次,可能每次获得的总数据量不一致,原因是还没到算出来的最后一页时,replies就不返回数据了(具体在get_comment_data()中三引号注释里),所以无法保证一定能获得完整数据。

    # -*- coding: utf-8 -*-
    # 上一行设置编码的代码不能删,否则有一句注释会报编码错误
    import requests
    import json
    import math
    import time
    import os
    import csv
    
    headers1={ # 获取oid时使用
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36 Edg/96.0.1054.34',
    }
    headers2={ # 获取评论内容时使用
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36 Edg/96.0.1054.34',
        'Referer':'https://t.bilibili.com/'
    }
    
    # url_for_get_oid='https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/get_dynamic_detail?dynamic_id=567647071398441903' # 打开一条具体的动态时fiddler抓的原始URL。需从返回的信息中获取oid,dynamic_id是浏览器打开一条具体的动态时,地址栏末尾的一长串字符
    url_for_get_oid='https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/get_dynamic_detail?dynamic_id={}'
    
    # url_for_get_comment='https://api.bilibili.com/x/v2/reply/main?callback=jQuery3310715507182874241_1637852200168&jsonp=jsonp&next=0&type=11&oid=164467640&mode=3&plat=1&_=1637852200169' # 在一条具体的动态页,页面滚动到底部第一次触发"正在加载..."时fiddler抓的原始URL。浏览器地址栏直接访问这个链接显示B站自定义的404页面,但去掉开头的callback参数和末尾的_参数后,浏览器地址栏可直接访问,返回json数据,另外测试发现plat参数也可去掉
    # https://api.bilibili.com/x/v2/reply/main?callback=jQuery3310715507182874241_1637852200168&jsonp=jsonp&next=2&type=11&oid=164467640&mode=3&plat=1&_=1637852200171 在一条具体的动态页,页面滚动到底部第二次触发"正在加载..."时fiddler抓的原始URL。除了末尾的_参数,就只有next参数变了,说明next参数和页码有关,因_参数不是必需的
    url_for_get_comment='https://api.bilibili.com/x/v2/reply/main?jsonp=jsonp&next={next}&type={type}&oid={oid}&mode={mode}'
    
    comment_detail_list=[] # 所有评论的详情,列表中每一条评论是一个字典
    
    
    def check_json_data(r): # 检查返回的数据是否是json格式
        try:
            json_data=r.json()
        except json.decoder.JSONDecodeError as e:
            raise Exception('发生了异常,可能因返回的数据不是json格式,或检查传入的url_for_get_oid及参数是否有误,具体异常为:'+'json.decoder.JSONDecodeError: '+str(e)) # raise json.decoder.JSONDecodeError()需三个参数,所以直接raise Exception();而Exception()中异常提示只能是字符串类型,这里的e是JSONDecodeError类型,所以强制转换;另外,这种写法输出异常提示时没有异常类型,所以以字符串形式手动加上
        except:
            raise Exception('发生了未捕获的异常')
        else:
            return json_data
    
    
    def get_oid_and_type_and_total_page_number(url_for_get_oid, dynamic_id): # 获取url_for_get_comment中用的oid和type的值,和页码总数
        r=requests.get(url=url_for_get_oid,headers=headers1)
        json_data=check_json_data(r)
    
        try:
            type=json_data['data']['card']['desc']['type']
            total_comment_number=json_data['data']['card']['desc']['comment']
        except KeyError: # 返回的数据中没有指定的键,导致获取不到对应的值,说明当前URL中没有需要的json数据,即URL有误
            raise KeyError('无效的dynamic_id,请重新运行输入')
        except:
            raise Exception('发生了未捕获的异常')
        
        if type==2:
            oid=json_data['data']['card']['desc']['rid']
            type_in_url_for_get_comment=11
        elif type in [1,4,2048]:
            oid=dynamic_id
            type_in_url_for_get_comment=17
        else:
            print(f'当前type变量的值{type}未被if捕获,需重新分析URL中type参数的值的所有可能')
            exit()
        
        total_page_number=math.ceil(total_comment_number/20)+19 # math.ceil(x)返回大于等于x的最小整数。每页20条数据,手动加19页防止页码总数由于各种原因计算错误,导致获取的数据不完整,且多出的页码由于没有数据会直接终止for循环
        return oid, type_in_url_for_get_comment, total_page_number
    
    
    def get_mode(): # 获取url_for_get_comment中用的mode
        while True:
            sort_by=input('请输入评论的排序方式,3表示按热度排序,2表示按时间排序,最好不按时间排序,原因在save_data()的except语句的注释中:')
            if (sort_by=='3') or (sort_by=='2'):
                return sort_by
            else:
                print('无效的排序方式,请重新输入')
    
    
    def has_comment_data(json_data): # 判断本页是否有评论,没有就直接终止for循环
        try:
            comment=json_data['data']['replies']
        except:
            raise Exception('发生了未捕获的异常')
        else:
            if comment:
                return True
            else:
                print('本页无数据,爬取结束')
                return False
        
        '''也可以这样判断,需配合get_comment_data()中三引号注释中写法使用
        try:
            isEnd=json_data['data']['cursor']['is_end']
        except:
            raise Exception('发生了未捕获的异常')
        else:
            if str(isEnd) in ['False','false']:
                return True
            else:
                print('这是最后一页,下一页无数据,爬完这一页就爬取结束')
                return False
        '''
    
    
    def get_comment_data(json_data): # 获取评论内容,不含对评论的回复
        try:
            replies_list=json_data # 自行从字典json_data中提取需要的数据
        except:
            raise Exception('发生了未捕获的异常')
        else:
            pass # 最核心的代码已删除
            
            
            '''has_comment_data()中若用判断is_end的方法,则get_comment_data()中try语句的else语句内,for循环遍历replies_list前,应加一句if判断,使整个for循环在这个if内
            if isinstance(replies_list,list) and len(replies_list)>0: # 有时当前页码返回的json数据中is_end是False,说明不是最后一页(即便是,最后一页也有数据),但此时replies中无数据,返回的replies可能是null(火狐浏览器访问接口看的,不是字符串,对应python中的NoneType类型)也可能是空列表,为确保后面页码的数据也能获取到(如果真的有数据),所以这里判断replies_list的类型和长度,只有它是列表且长度大于0时才说明本页有评论数据
            '''
    
    
    def save_data(dynamic_id,oid,type): # 保存所有评论的详情
        dir_path=os.path.abspath(os.path.dirname(__file__))
        file_name=f'B站动态ID{dynamic_id},oid{oid},type{type}的评论.csv' # 用代码自动删除评论时需动态的oid和type,写在文件名中方便读取
        file_path=dir_path+'/'+file_name
        try:
            header=comment_detail_list[0].keys()
        except IndexError: # 按时间排序时由于部分页码(目前我发现的是页码为1和页码为2)返回的评论为空导致直接终止for循环,然后执行save_data(),若刚好这些页码是开始爬取的页码,则comment_detail_list中无数据,执行save_data()中try语句触发IndexError异常
            raise IndexError('comment_detail_list中无数据,数据保存失败')
        except:
            raise Exception('发生了未捕获的异常')
        else:
            # 不同保存方式的注释在bs4爬取招聘.py中
            with open(file_path,'w',newline='',encoding='utf-8') as f:
                writer=csv.DictWriter(f,fieldnames=header)
                writer.writeheader()
                writer.writerows(comment_detail_list)
            print('数据已保存到本地')
    
            '''
            string=json.dumps(comment_detail_list,ensure_ascii=False) # 将数据以json格式保存。ensure_ascii=False使json中的中文正常显示
            with open(dir_path+'/'+f'B站动态ID{dynamic_id},oid{oid},type{type}的评论.txt','w',encoding='utf-8') as fp:
                fp.write(string)
            print('数据已保存到本地')
            '''
            '''
            with open(dir_path+'/'+f'B站动态ID{dynamic_id},oid{oid},type{type}的评论.txt','w',encoding='utf-8') as f:
                f.write(str(comment_detail_list)) # 将列表直接转为字符串写入文件
            print('数据已保存到本地')
            '''
    
    
    if __name__=='__main__':
        dynamic_id=input('请输入dynamic_id的值,它是浏览器打开一条具体的动态时,地址栏末尾的一长串字符:')
        url_for_get_oid=url_for_get_oid.format(dynamic_id)
        oid, type, total_page_number=get_oid_and_type_and_total_page_number(url_for_get_oid, dynamic_id)
        mode=get_mode()
    
        print(f'共有{total_page_number}页,这个数字已加入冗余,防止由于各种原因计算错误,导致获取的数据不完整,且多出的页码由于没有数据会直接终止for循环')
        start_page_number=int(input('请输入开始爬取的页码:')) # for循环中需是int类型
        end_page_number=int(input('请输入结束爬取的页码:'))
        if start_page_number==0: # next为0或1都返回第一页的数据
            start_page_number=1
        loop_count=0 # 记录循环执行次数
        for page_number in range(start_page_number,end_page_number+1):
            print(f'第{page_number}页开始爬取')
            comment_url=url_for_get_comment.format(next=page_number,type=type,oid=oid,mode=mode) # 注意等号左面不能写url_for_get_comment,会覆盖全局变量,导致next的值永远为第一轮循环中page_number的值,即start_page_number的值
            r=requests.get(url=comment_url,headers=headers2)
            json_data=check_json_data(r)
            if not has_comment_data(json_data): # 函数内若用三引号注释中写法,则这里要调整调用位置,调整到get_comment_data()之后,sleep()之前(最好紧挨sleep()),因若当前页是最后一页,则需先执行get_comment_data()获取当前页数据,再执行break终止循环
                break
            get_comment_data(json_data)
            loop_count=loop_count+1
            print(f'第{page_number}页结束爬取')
            print() # 打印空行
            time.sleep(3)
        save_data(dynamic_id,oid,type)
        print(f'获取数据共执行了{loop_count}次循环,每页20条数据(有时18条),假设最后一页不足20条,则去重后的数据量应大于({loop_count}-1)*20={(loop_count-1)*20}条,请打开文件核对数据量是否正确。若数据量不足,根据需求决定是否重新爬取,如针对有些需求,比完整数据稍微少一些也不影响。')

    参考链接:

    手把手教你爬取B站动态下的评论(看了必会,2个字简单) - 哔哩哔哩

    爬虫实战 - 如何爬取B站视频评论? - phyger - 博客园

    【python爬虫】爬取bilibili动态下面的评论 - 哔哩哔哩

    bilibili动态评论的爬取: 通过b站开放的APL接口进行一个评论的爬取,只爬取的评论本身内容,发布评论的网友信息没有进行一个爬取

    第四个与第三个内容一样,但可读性更好

    我在get_comment_data()末尾,只是简单判断新爬到的数据是否在已有数据中已存在,关于数据去重,可看以下参考链接:

    Python数据去重_夜空下的凝视-CSDN博客_python 去重

    Python判断列表里是否有重复元素的三种方法_宁宁Fingerstyle的博客-CSDN博客_python重复元素判定
    python常用的去重方式 - 星牧 - 博客园

    左手用R右手Python系列8——数据去重与缺失值处理 - 云+社区 - 腾讯云

    python数据去重(pandas)_Oliver、He的博客-CSDN博客_python数据去重

    展开全文
  • “老师说会给我们时间继续完善这个作业,直到可以真的爬到微信朋友圈内容….”,其实之后前面半句是有,但是后面半句真的爬到朋友圈却没有了,老师改变了需求,我们变成了去爬一些旅游网站了。我们被分派到的任务是...

    前言

    最近考试一直都没有时间写这篇总结,现在考试暂告一段落,现在抽空出来写一篇总结,总结一下python爬虫的学习进度。

    承接上一篇基于scrapy框架爬虫学习小结,上一篇主要是第二次作业后,“老师说会给我们时间继续完善这个作业,直到可以真的爬到微信朋友圈内容….”,其实之后前面半句是有,但是后面半句真的爬到朋友圈却没有了,老师改变了需求,我们变成了去爬一些旅游网站了。

    我们被分派到的任务是:研究分析携程,艺龙,去哪儿,途牛和驴友的爬去规则分析以及爬取他们的酒店评论,我分配到的是携程和艺龙的酒店评论的爬取。

    刚开始是我想用之前用过的scrapy来做这个爬取,但是不知道是不是我用的不太熟练,了解不深入,导致一些问题我自己没有办法处理,而且上网搜索的一些答案我都看不懂,之后我才直到,scrapy如果自己没有一定实力,排错很困难。

    所以还是回到底层python爬虫实现,凡是要一步步来,因为如果我发现网上许多python爬虫都是用urllib,urllib2,bs4,Request等库来实现的,所以先抛开scrapy框架来,回归一些更加基础的爬虫相关库的学习。
    (在进行下面之前请执行pip list查看自己是否已经安装了beautiful soup,lxml,Request和scrapy等第三方库,urllib,urllib2,re和json都是内置库不用考虑,如果没有上面四个第三方库,请用pip install之)

    分析要爬取的网站

    ①携程

    http://hotels.ctrip.com/hotel/

    上面这个是携程酒店页,广度优先的话就是这一层先完成爬去每个酒店评论页的URL,携程酒店页长这样:
    这里写图片描述

    然后再在每个url里面找到评论数据来源再抓下来,这里我们选择上图第一个酒店桔子酒店(北京天宁寺店)为例子,点进去长这样:

    这里写图片描述

    看到图中酒店评论(1423)没有,点开看看:
    这里写图片描述

    貌似我们的要的评论就近在咫尺,但是当我们打开网页源码我们发现它的评论只有第一页的5条,后面的没有了,那么剩下的评论(第2,第3页…)又是怎么获得的呢?

    AJAX:这里就得说说AJAX了,本身什么意思我就不详细说了,我们只要知道AJAX技术是一种异步加载数据的方式,在原来静态网页的天下,每次点击都要重新从服务器再次请求整个网页下来,而其实中间我们需要更新的数据只有一小部分(如果原来请求整个网页是100%的话,那么这一小部分只有5%),这样就造成了大量的带宽浪费,AJAX就是当需要更新部分数据时候,只从服务器加载这一小部分数据,而不用把整个页面又重新下载一遍,AJAX属于前后端数据交互技术,那么我们又要怎么观察AJAX的交互呢?又得如何获得这些动态加载的酒店评论数据呢?我自己用的是chrome所以推荐chrome浏览器自带的开发者工具【Ctrl】+【Shift】+【I】,就可以调出来了,长这样:

    这里写图片描述

    图中我们可以看到现在是评论的第二页,右边啥都没有,先点到Network,再选择筛选器,原来是【All】,现在换成【XHR】(大部分重新加载的AJAX在浏览器中都属于XHR类,当然有部分还可能在JS 类中),这样更加方便找出通过AJAX加载的更新数据,接下就刷新一下页面,让它重新加载第二页评论,结果如图:

    这里写图片描述

    P.s.我这里试了一下,直接刷新会回到最开始的这个页面。最好的办法就是,先点进酒店评论(1420),此时为第一页,打开chrome的开发者工具,选择XHR筛选器,然后点击下一页,就会得到一模一样的上图

    我们可以看到,在右侧的这个URL的内容里面有很多有价值的信息,有Request URL,往下翻,可以看到Reques Headers(Cookie,Referer,User-Agent等伪装头部必备元素~)内容,这里上张图更清晰:

    这里写图片描述

    一般来说,通过Request URL,我们就可以抓到更新加载的评论数据了,但是这个URL打开是这样的,一片空白,不对啊,以往经验来说应该是有显示出json格式的数据的!!后面我以为是请求头request headers或者是referer(有些网站会检查这个请求来源URL,这个referer记录的就是请求来源URL)的伪装问题,但是写上去了问题依旧,但是通过Preview倒是能够看到加载的内容:

    这里写图片描述

    我这里纠结了好几天,也尝试很多办法,可能携程有特殊的反爬虫手段?现在目测只能后面再试试通过Selenium+PhantomJS(无头浏览器)模仿用户正常请求来试试了,但是Selenium也要做功课的呀,外加这篇讲传参的就不涉及了。考虑到最近要准备期末考试了,所以暂时把携程酒店评论抓取的工作挂起,以后有空再折腾- -b

    P.s.今天在开发者头条看到携程酒店研发部研发经理崔广宇的一个分享,感觉之后可以参考再分析

    ②艺龙

    艺龙总的来说给我的感觉没有携程那么复杂,可能携程太出名了吧,又广告,各种耳闻。

    http://hotel.elong.com

    上面这个是艺龙酒店页面,我们以广州的酒店为例子,谁叫我们是广州仔呢,广州的话就是这个:

    http://hotel.elong.com/guangzhou/

    和携程一样又是一列的酒店,长这样:

    这里写图片描述

    我们也可以广度优先先爬去每个酒店的URL,不过这个先放着,我们先看看点进去每个酒店的评论页面,先分析一番,这里我们以上图中的广州珠江新城希尔顿欢朋酒店为例,下面就是点进去,点击点评,呈现出来长这样:

    这里写图片描述

    惯例对比网页源码和网页内容(这里指评论),我们发现和携程一样,艺龙酒店评论页面的源代码中只有5条评论,其他估计也是通过AJAX异步加载的,通过chrome的开发者工具分析交互过程,果然如此:

    这里写图片描述

    上图中是我通过分析评论加载过程得出的,中间省略了一些步骤。

    P.s.强烈建议你花点时间琢磨一下如何利用开发者工具分析交互过程,这有助于你快速找出AJAX加载地址URL

    详细的信息:
    这里写图片描述

    这里写图片描述

    把General下的Request URL复制粘贴到浏览器地址栏,打开,会有这样的返回

    这里写图片描述

    和携程不一样,我们直接打开AJAX请求的URL能够如期地得到我们想要的json格式数据,接下来我们请求这个URL就可以了,不用像静态网页那样通过re(正则表达式)或者xpath定位到某个标签了。

    做到这里,貌似我们已经找到我们想要的数据了,别着急,我们再来分析一下这个AJAX请求的URL的规律:

    http://hotel.elong.com/ajax/detail/gethotelreviews/?hotelId=91245064&recommendedType=0&pageIndex=1&mainTagId=0&subTagId=0&=1467394392854

    1.我注意到上面的URL中有hotelId字段,这里是91245064,和原来这个【http://hotel.elong.com/guangzhou/91245064/】中的数字一样,我就想是不是每个酒店都有属于自己的一个编号,那么为了验证我的想法,我就打开了【http://hotel.elong.com/guangzhou/32001199/】——广州东山宾馆的页面,同样出现了32001199这一串数字:

    这里写图片描述

    我们可以看到,这次AJAX请求的URL是:

    http://hotel.elong.com/ajax/detail/gethotelreviews?hotelId=32001199&recommendedType=0&pageIndex=1&mainTagId=0&subTagId=0&=1467395437371 

    hotelId是32001199和原本【http://hotel.elong.com/guangzhou/32001199/】 中的一样,所以我基本确定,这串数字就是每个酒店都有的编号。

    知道这个又有什么意义?首先知道了酒店有自己的酒店编号,然后又知道了原来通过AJAX请求的URL中的hotelId是等于这个这个酒店编号的,那么我们可不可以只是通过修改这个hotelId来获取不同酒店的酒店评论呢?答案是肯定的,我就不贴试验图了,你们可以自己做试验。

    2.再回来看1里面的hotelId=91245064的那个AJAX地址:

    http://hotel.elong.com/ajax/detail/gethotelreviews?hotelId=91245064&recommendedType=0&pageIndex=1&mainTagId=0&subTagId=0&=1467396059996 

    我们发现pageIndex=1,学过英文的我们能猜到pageIndex应该就是指页码,对比一下这个URL打开的json中的评论和原页面中第一页的评论内容,发现一致,修改pageIndex=2,回车,发现重新加载,内容改变,再对比重新加载的json数据内容和原页面中第二页的评论内容,发现也是一致的,基本可以肯定pageIndex的值就是确定请求第几页的值的字段

    3.通过上面两点对AJAX请求的URL地址的分析,我们知道了我们只要改变hotelId和pageIndex的值就可以请求完艺龙内所有酒店的评论数据,不过如果要用程序实现,我们还需要直到所有的hotelId和每一个酒店内评论最大页面数量

    程序实现

    1.第一步要做的是获取全部酒店hotelId
    (凡事都得小范围试验先,所以还是先拿广州开刀,试着抓第一页全部的酒店编号)

    #! usr/bin/python
    #coding:utf-8
    import urllib
    import urllib2
    import json
    
    from  bs4 import BeautifulSoup as bs
    import re
    
    '''
    crawl page from hotel.elong.com
    '''
    #this part can be replaced by requests 
    place="guangzhou/"
    url="http://hotel.elong.com/"+place
    
    #headers camouflage
    useragent='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36'
    headers={
    
        'User-Agent':useragent
    }
    
    req=urllib2.Request(url,"",headers)
    res=urllib2.urlopen(req)
    data=res.read()
    res.close()
    
    '''
    parse and select what i need
    '''
    #BeautifulSoup(data) will transform [data] in unicode automatically
    soup=bs(data)
    hotellinks=[]#define a list for hotellink
    hotelnumbers=[]#define a list for hotelbumber
    
    
    for tagdiv in soup.find_all('div',class_='h_info_base'):
        for tagp in tagdiv.find_all('p',class_='h_info_b1'): 
            for taga in tagp.find_all('a'):    
                hotellink=taga.get('href')
    
                #throw every hotellink to hotellinks list
                hotellinks.append(hotellink)
    
    for hotellink_to_hotelnumber in hotellinks:
        for hotelnumber in re.findall(r'/\d*/',hotellink_to_hotelnumber):
            for hotelnumber_again in re.findall(r'\d*',hotelnumber):
                hotelnumbers.append(hotelnumber_again)
    
    
    print hotellinks,"\n"
    print hotelnumbers,"\n" 

    以下为输出结果为第一页的全部酒店编号,以及URL路径:

    这里写图片描述

    P.s.酒店列表翻页也用了AJAX技术实现动态加载,但是和上面谈论的不一样的是,之前谈论的都是GET方法,但是这里POST方法,有点不一样,还没有深入研究,不过我知道的一点是,如果你直接找到它AJAX请求URL,然后直接打开是获取不了数据的,原因现在推测是请求头的“Content-Type:application/x-www-form-urlencoded;”这里出现的,更加准确的原因得继续研究实验POST方法才能得出。
    【也是尚未完成,没有完成后面全部页面酒店编号(hotelnumber_list)和路径的抓取,待续】

    2.第二步获取每个酒店的评论页面最大值(Pgnum_max)
    【未完成,待续】

    3.最后获取酒店评论信息

    由于前面两部已经获取了酒店编号列表和每个酒店评论页最大值,那么这一步就是基于hotelnumber_list和Pgnum_max做的一个函数(粗略示范一下):

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    
    import sys
    reload(sys)
    sys.setdefaultencoding('utf-8')
    
    import time
    import os
    from os.path import exists
    import json
    import codecs
    import gzip
    import StringIO
    
    import urllib
    import urllib2
    
    import re
    
    
    def getComment(hotelnumber,Pgnum_max):
    
    
        for page in xrange(Pgnum_max):
    
            Comments=[]
    
            page=str(page)
            print "第"+page+"页"
    
            url="http://hotel.elong.com/ajax/detail/gethotelreviews/?hotelId="+hotelnumber+"&recommendedType=0&pageIndex="+page+"&mainTagId=0&subTagId=0&_=1468730838292" 
            headers={
    
                'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36',
                'Accept':'application/json, text/javascript, */*; q=0.01',
                'Accept-Language':'zh-CN,zh;q=0.8',
                'Accept-Encoding':'gzip, deflate, sdch',
                'Connection':'keep-alive',
                'X-Requested-With':'XMLHttpRequest',
                #'Referer':'http://hotel.elong.com/guangzhou/32001005/',
                'Host':'hotel.elong.com'
    
            }
    
    
            #请求AJAX
            req=urllib2.Request(url,None,headers)
            res=urllib2.urlopen(req)
            data=res.read()
            res.close()
    
            #因为data有压缩所以要从内存中读出来解压
            data=StringIO.StringIO(data)
            gz=gzip.GzipFile(fileobj=data)
            ungz=gz.read()
    
            #正则提取
            pattern_Comment=re.compile(r'"content":"\W*"')
            #仍不能完全匹配到所有用户,这里只是匹配到中文名的用户,可能要用到非贪婪模式!
            pattern_username=re.compile(r'"nickName":"\W*"')
            pattern_CommentTime=re.compile(r'"createTimeString":"....-..-..\s..:..:.."')
    
    
            for username in re.findall(pattern_username,ungz):
                # print type(username)
                # print username,'\n'
                Comments.append(username)
    
    
            for CommentTime in re.findall(pattern_CommentTime,ungz):
                # print type(CommentTime)
                # print CommentTime,'\n'
                Comments.append(CommentTime)
    
    
            for Comment in re.findall(pattern_Comment,ungz): 
                # print Comment,'\n'
                # print type(Comment)
                Comments.append(Comment)
    
            #保存成HTML
            comment=",".join(Comments)#list转str
    
            cdir = u'./Comments/'
            if not exists(cdir):
                os.makedirs(cdir)
    
            f=codecs.open(cdir+u'page'+page+u'.txt','wb+','utf-8')
            f.write(comment)
    
    '''
    程序入口
    '''
    #前面也是没有做完,所以酒店编号列表先构造一个
    hotelnumber_list=['32001005']
    #前面没有抓到最大评论页面数,先假定最大有200页
    Pgnum_max=2
    for hotelnumber in hotelnumber_list:
        getComment(hotelnumber,Pgnum_max)

    下面是用存储的page0.txt(商品编号为32001005的第一页信息)作为示范,吐槽一下自己,因为偷懒所以直接在windows下开putty用vim打开的page0.txt截的图,现在贴上去后觉得丑死了….
    这里写图片描述

    免责声明

    本篇文章只是为了分享技术和共同学习为目的,并不出于商业目的和用途,也不希望用于商业用途,特此声明。

    展开全文
  • python爬虫实战---爬取大众点评评论

    千次阅读 2020-08-27 20:25:37
    很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手。 很多已经案例的人,却不知道如何去学习更加高深的知识。 那么针对这三类人,我给大家提供一个好的学习平台,免费领取视频教程,电子书籍,以及...
  • 笔者最近迷上了数据挖掘和机器学习,要数据分析首先得有数据才行。对于我等平民来说,最廉价的获取数据的方法,应该是用爬虫在网络上爬取数据了。本文记录一下笔者爬取天猫某商品的全过程,淘宝上面的店铺也是类似...
  • 评论在数据库中存储!!

    千次阅读 2016-09-02 20:28:00
    而这些不同的动作对应的数据其实是存在不同的表中,例如话题表、回帖表、评论表等等。 今天主要是介绍 OSChina 是如何将这些属于不同范围的数据汇总到用单一时间轴进行展示的动态。 动态表 首先要说明的是动态表...
  • 会抓个网页总感觉还不够,平时在空间里经常会遇到秒赞或者是秒评论的,现在也可以自己用爬虫在qq空间...1.1 首先先用cookie登录qq空间,抓取到页面的内容,用pc端打开自己的qq空间,按F12进入开发者模式,可以看...
  • Freepik,是一个国外的设计素材网站,上面的设计资源非常丰富,你可以找到矢量图、位图、Ps和Ai模板。 网站提供的素材有免费与收费之分,对于免费的素材,个人使用或商业使用时,都需要附上指向源素材的链接。而对于...
  • Z-library推荐指数:★★★★★网站:https://z-lib.org/这个网站据称是世界最大的电子图书馆,收藏的资源包含725万+本书、8075万+的文献条目,可以说是相当丰富了...
  • 爬取Google Play中app的用户评论(1)

    千次阅读 热门讨论 2018-09-22 22:21:43
    第一次写爬虫,真的踩了不少的坑 坑1: 看了爬虫视频后,首先尝试用request库和beautifulsoup来爬取Google ...总之,在巧合之下,我解决了这个问题 现在两台电脑都可以写爬虫了 在也不用把电脑背来背去了 开森
  • 以我差不多四年的 Python 使用经验来看,大概可以按以下这些路子来赚到钱,但编程技能...如果你都不知道的话问题也不大,可以考虑自己开一个淘宝网店或猪八戒服务外包。很多人可能会觉得开淘宝店很困难,其实不...
  • 原标题:用python爬过这些网站,才敢说自己会爬虫! Python爬虫:爬过这些网站,才敢说自己会爬虫!摘要:微信、知乎、新浪等主流网站的模拟登陆爬取方法。网络上有形形色色的网站,不同类型的网站爬虫策略不同,难...
  • 很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手。 很多已经案例的人,却不知道如何去学习更加高深的知识。 那么针对这三类人,我给大家提供一个好的学习平台,免费领取视频教程,电子书籍,以及...
  • 旅游评论情感分析(1)---爬虫(json篇)

    千次阅读 2019-02-15 17:07:33
    因为,暑假实训的关系认识了一个妹子是学习旅游专业,他们需要对我国某一个地方的旅游景点需要进行考察,除了他们之后实地考察外,我们小组的实训任务是帮助他们在网上下载阳朔的旅游景点的评论。并加以分析情感。 ...
  • 有小伙伴在微信上向我反馈说,他在面试中被问到这个问题,微信朋友圈为啥把“赞”和“评论”隐藏起来,而QQ空间却把“赞”和“评论”放出来?面试官问题背后的动机这个题目面试官更多的考察求职者对...
  • 前言:wordpress算是目前使用最多的博客搭建程序,包括我自己的站点:吃饭用大碗的程序猿 都是用wordpress搭建的,目前选的模板比较简洁,毕竟一开始我就打算一个简洁一点的博客网站。不过后期我还是会换的,哈哈...
  • 这个Bug很可能会在一年两年后再次遇到,如果没有记录,大多情况下还是得花同样的时间来解决这个Bug。 写博客的好处有很多很多(这里我就不赘述了),常常谈到这里的时候,别人就会问我:“3y你推荐哪个博客平台去写...
  • 在分享之前,先说说初学者如何学习编程,这个话题想必非常的重要,要学好编程,给你一些学习网站也好、实用工具也好,但前提是你知道如何去学习它。 见过很多初学者,以及小鹿我刚开始学习的时候,也是自己瞎摸索,...
  • 豆瓣评分与观众 对于一部国产网剧,如果想分析它的评分与评价,那么似乎只有豆瓣这一个网站可以选择了。 而每一个学python的人,应该写过豆瓣,拉勾,网易云“三剑客”的爬虫吧? 那么爬虫部分就不一一赘述了。 ...
  • 29.保姆级教程带你一个属于自己的博客网站

    万次阅读 多人点赞 2021-09-15 13:25:15
    将数据库中所有博客展示到前端页面中,点击文章可以查看文章的详情,附带编辑和删除的功能 详情页detail.html 显示文章的标题及内容 1.环境配置及Django初始化 (1)ubuntu中新建供本项目使用的虚拟环境: 在...
  • 几个SQL在线刷题的网站

    千次阅读 2021-02-02 19:50:49
    每天更新,大概率是晚9点大家好,朱小五最近在知识星球分享了几个在线SQL刷题的网站,今天正好给大家详细介绍推荐一下。SQLZOO (⭐⭐)https://sqlzoo.net/上面的网址是一个免费的SQL学习网站——SQLZOO!它支持语言...
  • 其实登录一个网站也好,评论一篇文章也罢,甚至是发表一篇博文,其实都是网站跟“黑客”之间的较量,网站使用各种隐藏的黑科技去验证你是否是机器人,即使这可能会影响体验,而“黑客”则是抓取一切可以利用的东西,...
  • researchcode 网站地址:https://researchcode.com/ 2. paperswithcode 网站地址:https://paperswithcode.com/ 国外出名的学者一般有个人主页,有些代码可以在上面找到 3. semanticscholar 网站地址:...
  • 所以现在我们提前布局好今日头条的搜索排名,自然后续我们就可以早一步给企业带来更多的流量,今天牛商网就给讲解一下:今日头条搜索排名seo怎么?今天头条网站优化规则揭秘! 一、想要做好今日头条排名seo就要...
  • 当时自己比较关注“小凤雅事件”,而微博又是舆论的战场,就想爬取“小凤雅事件”的相关微博以及评论,看看大家的关注点在哪里,于是就行动起来了。 下面是对主要技术的介绍。 二、scrapy介...
  • 关于天猫(淘宝)评论爬虫

    千次阅读 2018-02-23 08:41:04
    我们已经找到这些数据包,需要访问这些数据包仍然需要访问头,带着访问头去访问网页我自认为是一个好习惯,虽然有时候这样显得很臃肿,但是这样可以减少很多问题。在你成功访问到内容后,利用beautifulsoup、正则...
  • 大众点评评论标签替换文字问题

    千次阅读 2019-01-08 17:10:27
    大众点评评论爬虫的时候发现评论内容会有部分文字被 b标签替换掉 如图: 一看,这是什么鬼,没见过啊,怎么办,然后选中一个 b标签,看到右边的css,如图 注意到background-image:url 这里比较可疑,打开看一下...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 22,713
精华内容 9,085
关键字:

哪里可以做可以评论的网站