修改Selenium源码
webMagic中已经封装了selenium模块的代码,但官方版本的代码有些地方需要修改,我们下载源码后要自己简单改动一下然后重新编译。我这下载了0.8.1-SNAPSHOT
版本的代码,官方git地址:
修改配置文件地址,在WebDriverPool
将selenium
配置文件路径写死了,需要改变配置路径:
// 修改前 // private static final String DEFAULT_CONFIG_FILE = "/data/webmagic/webmagic-selenium/config.ini"; // 修改后 private static final String DEFAULT_CONFIG_FILE = "selenium.properties";
在resources
目录下添加配置文件selenium.properties
:
> 基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能 > > * 项目地址:<https://github.com/YunaiV/yudao-cloud> > * 视频教程:<https://doc.iocoder.cn/video/> # What WebDriver to use for the tests driver=chrome # PhantomJS specific config (change according to your installation) chrome_driver_loglevel=DEBUG
js模拟页面操作
修改SeleniumDownloader
的download()
方法,在代码中的这个位置,作者很贴心的给我们留了一行注释:
意思就是,你可以在这添加鼠标事件或者干点别的什么东西了。我们在这添加页面向下滚动这一模拟事件,每休眠2s就向下滚动一下页面,一共下拉20次:
//模拟下拉,刷新页面 for (int i=0; i < 20; i++){ System.out.println("休眠2s"); try { //滚动到最底部 ((JavascriptExecutor)webDriver) .executeScript("window.scrollTo(0,document.body.scrollHeight)"); //休眠,等待加载页面 Thread.sleep(2000); //往回滚一点,否则不加载 ((JavascriptExecutor)webDriver) .executeScript("window.scrollBy(0,-300)"); } catch (InterruptedException e) { e.printStackTrace(); } }
修改完成后本地打个包,注意还要修改一下版本号,改成和发行版的不同即可,我这里改成了0.8.1.1-SNAPSHOT
。
mvn clean install
调用
回到之前的爬虫项目,引入我们自己打好的包:
<dependency> <groupId>us.codecraft</groupId> <artifactId>webmagic-selenium</artifactId> <version>0.8.1.1-SNAPSHOT</version> </dependency>
修改之前的主程序启动时的代码,添加Downloader
组件,SeleniumDownloader
构造方法的参数中传入我们下好的chrome的webDriver的可执行文件的地址:
public static void main(String[] args) { Spider.create(new WenxinProcessor()) .addUrl("https://www.zhihu.com/question/589929380") .thread(2) .setDownloader(new SeleniumDownloader("D:\\Program Files\\Google\\Chrome\\Application\\chromedriver.exe") .setSleepTime(1000)) .run(); }
进行测试,可以看到在拉动了40秒窗口后,获取到的答案条数是100条:
通过适当地添加下拉页面的循环的次数,我们就能够获取到当前问题下的全部回答了。
另外,在启动爬虫后我们会看到webDriver弹出了一个chrome的窗口,在这个窗口中有一个提示:Chrome正受到自动测试软件的控制 ,并且可以看到页面不断的自动下拉情况:
如果不想要这个弹窗的话,可以修改selenium模块的代码进行隐藏。修改WebDriverPool
的configure()
方法,找到这段代码:
if (driver.equals(DRIVER_CHROME)) { mDriver = new ChromeDriver(sCaps); }
添加一个隐藏显示的选项,并且在修改完成后,重新打包一下。
if (driver.equals(DRIVER_CHROME)) { ChromeOptions options=new ChromeOptions(); options.setHeadless(true); mDriver = new ChromeDriver(options); }
获取问题详细描述
不知道大家还记不记得在前面还留了一个坑,我们现在获取到的对问题的描述是不全的,需要点一下这个按钮才能显示完全。
同样,这个问题也可以用selenium来解决,在我们下拉页面前,加上这么一个模拟点击事件,就可以获得对问题的详细描述了:
((JavascriptExecutor)webDriver) .executeScript("document.getElementsByClassName('Button QuestionRichText-more')[0].click()");
看一下执行结果,已经可以拿到完整内容了:
Pipeline
到这里,虽然要爬的数据获取到了,但是要进行分析的话,还需要进行持久化操作。在前面的webMagic的架构图中,介绍过Pipeline
组件主要负责结果的处理,所以我们再优化一下代码,添加一个Pipeline
负责数据的持久化。
由于数据量也不是非常大,这里我选择了直接存入ElasticSearch中,同时也方便我们进行后续的分析操作,ES组件我使用的是esclientrhl
,为了方便我还是把项目整个扔到了spring里面。
定制一个Pipeline也很简单,实现Pipeline
接口并实现里面的process()
接口就可以了,通过构造方法传入ES持久化层组件:
@Slf4j @AllArgsConstructor public class WenxinPipeline implements Pipeline { private final ZhihuRepository zhihuRepository; @Override public void process(ResultItems resultItems, Task task) { Map<String, Object> map = resultItems.getAll(); String title = map.get("title").toString(); String question = map.get("question").toString(); List<String> answer = (List<String>) map.get("answer"); ZhihuEntity zhihuEntity; for (String an : answer) { zhihuEntity = new ZhihuEntity(); zhihuEntity.setTitle(title); zhihuEntity.setQuestion(question); zhihuEntity.setAnswer(an); try { zhihuRepository.save(zhihuEntity); } catch (Exception e) { e.printStackTrace(); } } } }
把selenium向下拉取页面的次数改成200后,通过接口启动程序:
@GetMapping("wenxin") public void wenxin() { new Thread(() -> { Request request = new Request("https://www.zhihu.com/question/589929380"); WenxinProcessor4 wenxinProcessor = new WenxinProcessor4(); Spider.create(wenxinProcessor) .addRequest(request) .addPipeline(new WenxinPipeline(zhihuRepository)) .setDownloader(new SeleniumDownloader("D:\\Program Files\\Google\\Chrome\\Application\\chromedriver.exe") .setSleepTime(1000)) .run(); }).start(); }
运行完成后,查询一下es中的数据,可以看到,实际爬取到了673条回答。
另外,我们可以在一个爬虫程序中传入多个页面地址,只要页面元素具有相同的规则,那么它们就能用相同的爬虫逻辑处理,在下面的代码中,我们一次性传入多个页面:
Spider.create(new WenxinProcessor4()) .addUrl(new String[]{"https://www.zhihu.com/question/589941496", "https://www.zhihu.com/question/589904230","https://www.zhihu.com/question/589938328"}) .addPipeline(new WenxinPipeline(zhihuRepository)) .setDownloader(new SeleniumDownloader("D:\\Program Files\\Google\\Chrome\\Application\\chromedriver.exe") .setSleepTime(1000)) .run();
一顿忙活下来,最终扒下来1300多条数据。
分析
数据落到了ES里后,那我们就可以根据关键字进行分析了,我们先选择10个负面方向的词语进行查询,可以看到查到了403条数据,将近占到了总量的三分之一。
再从各种回答中选择10个正向词语查询,结果大概只有负面方向的一半左右:
不得不说,这届网友真的是很严厉…
Proxy代理
说到爬虫,其实还有一个绕不过去的东西,那就是代理。
像咱们这样的小打小闹,爬个百八十条数据虽然没啥问题,但是如果要去爬取大量数据或是用于商业,还是建议使用一下代理,一方面能够隐藏我们的IP地址起到保护自己的作用,另一方面动态IP也能有效的应对一些反爬策略。
个人在使用中,比较推荐的是隧道代理 。简单的来说,如果你购买了IP服务的话,用普通代理方式的话需要你去手动请求接口获取IP地址,再到代码中动态修改。而使用隧道代理的话,就不需要自己提取代理IP了,每条隧道自动提取并使用代理IP转发用户请求,这样我们就可以专注于业务了。
虽然网上也有免费的代理能够能用,但要不然就是失效的太快,要不就是很容易被网站加入黑名单,所以如果追求性能的话还是买个专业点的代理比较好,虽然可能价格不那么便宜就是了。
题外话
看了一大顿下来,从大家略显犀利的言辞来看,大家总体上对文心一言还是不那么满意的。毕竟,在有着CHAT-GPT
这么一个优秀的产品做背景板的前提下,这届网友可能没有那么好糊弄。
但是话又说回来,丑媳妇总得见公婆不是?提早暴露缺陷,也有利于国内的这些大厂,看清和一流AI产品之间的真实差距,知难而进,迎头赶上。
源码地址:https://github.com/trunks2008/zhihu-spider
参考资料:
http://webmagic.io/docs/zh/posts/ch1-overview/architecture.html
https://blog.csdn.net/panchang199266/article/details/85413746