记一次批量定时抓取微信公众号文章的实现

抓取前的说明和准备

本次抓取的选择的语言是java,本文章不会将整个工程的全部代码全部贴出,只会提供核心代码和抓取思路的说明。

数据的抓取

抓取文章的来源为搜狗微信网站,网站如下图。
在这里插入图片描述

抓取的思路如下

  1. 一般抓取微信公众号的文章都是以微信公众号的id为关键字 ,我们可以通过url+ keyword的形式直接跳转到想要抓取公众号页面,keyword即为想要搜索微信公众号的名称或者是id;
// 搜狗微信搜索链接入口
       String sogou_search_url = "http://weixin.sogou.com/weixin?type=1&query="
               + keyword + "&ie=utf8&s_from=input&_sug_=n&_sug_type_=";
  1. 为了避免网站对爬虫的初步拦截,我们可以使用Selenium (浏览器自动化测试框架)来伪装自己的爬虫,我们使用的chrome,这里需要注意自己的chrome版本与使用的webdriver的版本是对应的
       ChromeOptions chromeOptions = new ChromeOptions();
       // 全屏,为了接下来防抓取做准备
       chromeOptions.addArguments("--start-maximized");
       System.setProperty("webdriver.chrome.driver", chromedriver);
       WebDriver webDriver = new ChromeDriver(chromeOptions);
  1. 到达微信公众号列表页面,如下图,获取微信公众号链接。
    在这里插入图片描述
              // 获取当前页面的微信公众号列表
              List<WebElement> weixin_list = webDriver
                      .findElements(By.cssSelector("div[class='txt-box']"));
              // 获取进入公众号的链接
              String weixin_url = "";
              for (int i = 0; i <= weixin_list.size(); i++) {
                  String weixin_name = weixin_list.get(i)
                          .findElement(By.cssSelector("p[class='tit']"))
                          .findElement(By.tagName("a")).getText();
                  if (weixin_name.equals(keyword)) {
                      weixin_url = weixin_list.get(i)
                              .findElement(By.cssSelector("p[class='tit']"))
                              .findElement(By.tagName("a")).getAttribute("href");
                      break;
                  }
              }

              webDriver.get(weixin_url);
  1. 到达微信公众号文章列表页面,如下图;
    在这里插入图片描述

  2. 通过对网页元素的分析对微信文章相关信息进行抓取;

             // 获取微信文章列表
             List<WebElement> weixin_article_list = webDriver.findElements(
                     By.cssSelector("div[class='weui_media_box appmsg']"));
             for (WebElement weixin_article : weixin_article_list) {

                 // 获取文章缩略图url
                 String thumbNail = weixin_article
                         .findElement(By
                                 .cssSelector("span[class='weui_media_hd']"))
                         .getAttribute("style").substring(23);
                 String thumbNailUrl = thumbNail.substring(0,
                         thumbNail.length() - 3);
                 // 获取文章url
                 String articleUrl = "http://mp.weixin.qq.com"
                         + weixin_article
                                 .findElement(By.cssSelector(
                                         "h4[class='weui_media_title']"))
                                 .getAttribute("hrefs");
                 // 获取文章简介
                 String desc = weixin_article
                         .findElement(By
                                 .cssSelector("p[class='weui_media_desc']"))
                         .getText();
                 // 获取文章日期
                 String dateString = weixin_article
                         .findElement(By.cssSelector(
                                 "p[class='weui_media_extra_info']"))
                         .getText();
                 Date date = null;
                 try {
                     date = new SimpleDateFormat("yyyy年MM月dd日")
                             .parse(dateString);
                 } catch (ParseException e) {
                     // TODO 完成异常处理
                     e.printStackTrace();
                 }

                 Date today = new Date();

批量抓取

批量抓取的实现是通过Spring Batch来实现的。Spring Batch 是一个轻量级的、完善的批处理框架,旨在帮助企业建立健壮、高效的批处理应用。
Spring Batch 分为Reader,Processor,Writer,Listener等几个部分。因为是进行批量抓取,所以可以新建一个稿件抓取历史列表来判断是否为新的稿件,从而判断是否进行抓取。


@Configuration
@EnableBatchProcessing
@EnableScheduling
public class WechatOfficialAccountsStoryImportBatchConfig {
   
   @Bean
   public WechatOfficialAccountsStoryReader<WechatLink> wechatOfficialAccountsStoryReader() throws BusinessException, IOException {
       return new WechatOfficialAccountsStoryReader<WechatLink>();
   }
   
   @Bean
   public ItemProcessor<LinkHolder<WechatLink>, NrStory> wechatOfficialAccountsStoryProcessor() {
       return new WechatOfficialAccountsStoryProcessor();
   }

   @Bean
   public ItemWriter<NrStory> wechatOfficialAccountsStoryWriter() {
       return new WechatOfficialAccountsStoryWriter();
   }
   
   @Bean
   public Job importStoryWechatOfficialAccountsJob(JobBuilderFactory jobs, StepBuilderFactory stepBuilderFactory) throws BusinessException, IOException {
       return jobs.get("importStoryWechatOfficialAccountsJob")
               .incrementer(new RunIdIncrementer())
               .listener(listener(wechatOfficialAccountsStoryReader()))
               .flow(importStoryWechatOfficialAccountsStep(stepBuilderFactory))
               .end()
               .build();
   }
   
   @Bean
   public Step importStoryWechatOfficialAccountsStep(StepBuilderFactory stepBuilderFactory) throws BusinessException, IOException {
       return stepBuilderFactory.get("importStoryWechatOfficialAccountsStep")
               .<LinkHolder<WechatLink>, NrStory> chunk(1).faultTolerant()
               .skip(SkippingException.class).skip(BusinessException.class).skipLimit(100000)
               .reader(wechatOfficialAccountsStoryReader())
               .processor(wechatOfficialAccountsStoryProcessor())
               .writer(wechatOfficialAccountsStoryWriter())
               .build();
   }
   
   @Bean
   public WechatOfficialAccountsStoryJobListener listener(WechatOfficialAccountsStoryReader<WechatLink> reader) {
       return new WechatOfficialAccountsStoryJobListener(reader);
   }
}

定时抓取

Spring Batch支持定时抓取,具体规则参考Scheduled。


@Scheduled(cron = "0 0 7,9,11,13,15,17,23 * * ?")
   public synchronized void executeWechatOfficialAccountsStoryImport() throws BusinessException, IOException {
   	try {    
           long begin = System.currentTimeMillis();
   		LOG.info("############## 微信公众号定时任务开始 ##############");
   		Job importStoryJob = wechatOfficialAccountsConfig.importStoryWechatOfficialAccountsJob(
   				jobs, stepBuilderFactory);
   		JobParameters jobParameters = new JobParametersBuilder().addLong(
   				"time", System.currentTimeMillis()).toJobParameters();
   		JobExecution jobExecution = jobLauncher.run(importStoryJob,
   				jobParameters); 
   		LOG.info("jobExecution" + jobExecution);
   		long end = System.currentTimeMillis();
   		LOG.info("##############微信公众号定时任务结束,共耗时:[" + (end-begin) / 1000 + "]秒##############");

   	} catch (JobExecutionAlreadyRunningException e) {
   		LOG.info("微信公众号文章导入发生错误!" + e.getLocalizedMessage());
   	} 
   }

对爬虫防抓取机制的一些解决办法

如果抓取的频率较高,抓取的时候页面会要求输入验证码,我也尝试过许多图片识别的jar包,效果并不是很好。我们采用的是第三方的打码平台,有的时候人工还是比智能更好。我们选择的打码平台是快若打码,其官网上有他的使用方法,他提供了一些第三方接口。识别验证码后输入,继续进行抓取。


// 获取当前网页的标题
               String title = webDriver.getTitle();
               while (title.contentEquals("请输入验证码")) {
                   LOG.info("############## 微信公众号访问失败,需要输入验证码 ##############");
                   LOG.info("############## 开始识别验证码 ##############");

                   // 全屏截图
                   File srcFile = ((TakesScreenshot) webDriver)
                           .getScreenshotAs(OutputType.FILE);
                   Image src = Toolkit.getDefaultToolkit()
                           .getImage(srcFile.getPath());
                   BufferedImage originalImage = WechatOfficialAccountStoryJobUtils
                           .toBufferedImage(src);

                   WebElement verify = webDriver
                           .findElement(By.className("page_verify"));
                   Point location = verify.getLocation();
                   Dimension size = verify.getSize();

                   // 截取验证码
                   int getX = location.getX();
                   int getY = location.getY();
                   int getWidth = size.getWidth();
                   int getHeight = size.getHeight();
                   BufferedImage verifyImg = originalImage.getSubimage(
                           getX + getWidth / 3, getY + getHeight / 3,
                           getWidth / 2, getHeight / 3);

                   // 验证码的名称和路径
                   String dirName = "D:/verifyImg";
                   File dir = new File(dirName);
                   Date time = new Date();
                   String verifyImgPath = dirName + "/" + time.getTime()
                           + ".jpg";

                   // 验证码文件夹是否存在
                   if (dir.exists()) {
                       LOG.info("############## 目录已存在,无需创建 ##############");
                   } else {
                       LOG.info("############## 目录不存在,正在创建目录 ##############");
                       if (dir.mkdirs()) {
                           LOG.info("############## 目录创建成功 ##############");
                       }
                   }

                   // 下载验证码
                   try {
                       ImageIO.write(verifyImg, "jpg",
                               new File(verifyImgPath));
                   } catch (IOException e) {
                       // TODO 完成异常处理
                       e.printStackTrace();
                   }

                   LOG.info("############## 验证码下载成功 ##############");

                   // 识别验证码
                   String ret = WechatOfficialAccountStoryJobUtils
                           .createByPost(ruokuaiUsername, ruokuaiPassword,
                                   ruokuaiWeixintypeid, ruokuaiTimeout,
                                   ruokuaiSoftid, ruokuaiSoftkey,
                                   verifyImgPath);
                   String verifyLetter = ret.substring(37, 41);
                   LOG.info("############## 验证码识别成功 ,验证码为:##############");
                   LOG.info(verifyLetter);

                   // 输入验证码
                   WebElement input = webDriver.findElement(By.id("input"));
                   input.sendKeys(verifyLetter);

                   WebElement bt = webDriver.findElement(By.id("bt"));
                   bt.click();

                   // 刷新页面
                   webDriver.navigate().refresh();
                   title = webDriver.getTitle();

最后

以上就是这次抓取的全部内容了,虽然只提供了核心代码和抓取思路,但仍希望能够帮助到大家。
对了,如果需要部署项目的话,这部分功能需要部署到windows服务器,使用centos的话无法识别验证码。

好的,以下是一个简单的用php实现抓取微信公众号文字和图片的代码示例: ```php <?php // 设置请求头 $headers = [ 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3', 'Referer: https://weixin.sogou.com/', ]; // 获取文章链接 $url = 'https://mp.weixin.qq.com/s/xxxxxxxxxxxxx'; // 发送请求 $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $content = curl_exec($ch); curl_close($ch); // 解析HTML内容 $doc = new DOMDocument(); @$doc->loadHTML($content); // 获取文章标题 $title = $doc->getElementsByTagName('title')->item(0)->nodeValue; // 获取文章内容 $elements = $doc->getElementsByTagName('div'); $content = ''; for ($i = 0; $i < $elements->length; $i++) { $node = $elements->item($i); if ($node->getAttribute('class') == 'rich_media_content') { // 获取文字内容 $ps = $node->getElementsByTagName('p'); foreach ($ps as $p) { $content .= $p->nodeValue . "\n"; } // 获取图片链接 $imgs = $node->getElementsByTagName('img'); foreach ($imgs as $img) { $content .= $img->getAttribute('data-src') . "\n"; } break; } } // 输出结果 echo "文章标题: " . $title . "\n"; echo "文章内容: " . $content . "\n"; ?> ``` 需要注意的是,该代码仅供参考,具体实现还需要根据自己的需求进行调整。同时,需要遵循相关法律法规和微信公众平台的规定,避免违用户隐私和侵犯知识产权等问题。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值