制作从某网站中获得材料晶格参数的小爬虫总结
制作爬虫目的
由于师兄要求我查询十几个二维材料的所有禁带宽度、点阵常数,当我打开materialsProject网站,查找某个元素后,发现每个二维材料因为空间群的结构不同,也会有不同的禁带宽度和点阵常数。细数下如果要收集这些数据可能要打开一百多个网页,然后复制粘贴那些数值,为了解放机械性工作,所以凭借课余所学做了一个爬虫,来对这些数据抓取并打印下来。
了解网站,知道数据源文件的uri
首先打开该网站materialsproject,用“开发者工具进”行页面分析。搜索GaN,发现uri中包含了#符号,上网了解http不会请求#以后的数据,(关于http中带#号的介绍,可看https://blog.****.net/luka2008/article/details/38753269)。然后在分析中发现了一个uri是在点击“search”按钮后传回来的json文本,因此可以通过分析这个json文件的uri,来抓取相关数据,返回json文本的uri为
https://materialsproject.org/apps/materials_explorer/results?query=%7B%22reduced_cell_formula%22%3A%22GaN%22%7D 改其中红字上的内容,就可以获得想搜索的内容了
搜索结果的页面如下:
图中红色框框里面的内容是我想获取的,因此解析json文件,将material_id和其值放到map对象的键值对中,再把每个map对象通过push()放到mapList数组中。
当点击上图的列表中的其中一行,会跳转到该id值材料的详细页面中,这时通过分析页面会发现也会收到一个json文件,是关于晶格常数的。其uri为通过上述方法,把所需数据放到数组中,当请求完网页后便可以将数据一一打印出来。
爬虫操作方法:通过node运行文件后,在控制台中输入所有想操作的分子式,以空格分开,回车后便运行,并且打印出自己想要的数据,成功避免一次流水线任务,haha...
实现原理:
- 此爬虫使用nodejs实现的,用到了‘request-promise’模块以方便用async/await方法来等待收据接收完再放入数组并打印出来。
- 在控制台输入的各个分子式在通过‘line’事件被监听后,会被放入到formulaArray数组中。然后通过遍历每个分子式,分别进行json文件请求
- 在getValue()方法中,option对象的键值对是copy“开发者工具”分析页面时上的数据的,请求该uri,解析json文件,并把需要的数据放到map对象中,再把map对象填入mapList数组里面
- 然后再通过访问数组对象中材料id获取第二个json文件,其中其晶格参数有ICSD(无机晶体学数据库)的也有computed的,这里优先选取ICSD的,若ICSD不存在,再选取后者的。
- 最后通过遍历数组的对象,遍历对象的键值对,把其打印出来
爬虫代码
- const rp=require('request-promise');
- let mapList=[];//存放map对象的数组
- const readLine=require('readline');
- const rl=readLine.createInterface({
- input:process.stdin,
- output:process.stdout
- });
- rl.on('line',async function(line){
- let formulaArray=[];//存放分子式的数组
- if (line.trim()) {
- formulaArray=line.split(' ');
- for(let index=0;index<formulaArray.length;index++){//遍历每个分子式
- await getValue(formulaArray[index]);//请求网页并存储map于mapList数组中
- }
- console.log('打印结束');
- }
- });
- rl.on('close', function () {
- console.log('程序结束');
- process.exit(0);
- });
- async function getValue(formula){
- let options = {
- uri: 'https://materialsproject.org/apps/materials_explorer/results?query=%7B%22reduced_cell_formula%22%3A%22'+formula+'%22%7D',
- method: 'GET',
- headers: {
- 'Accept': 'application/json, text/javascript, */*; q=0.01',
- 'Accept-Language': 'zh-CN,zh;q=0.9',
- 'Connection': 'keep-alive',
- 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
- 'Host': 'materialsproject.org',
- 'Referer': 'https://materialsproject.org/',
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
- },
- json:true//此行会自动把传回来的数据进行json解析
- };
- await rp(options)//获取第一个json文件中的id、分子式、空间群符号和分子的禁带宽度
- .then(function(res){
- for(let i=0;i<res.length;i++){
- let map=new Map();
- map.set('materials_id',res[i].materials_id);
- map.set('formula',res[i].formula);
- map.set('symbol',res[i].spacegroup.symbol);
- map.set('bandgap',res[i]['band_gap (eV)']);
- mapList.push(map);
- //console.log(i);
- }
- })
- .catch(function(err){
- console.log(err);
- });
- await getLatticeValue();//获取第二个json文件中的点阵常数数据
- for(let i=0;i<mapList.length;i++){//打印数据
- let strLine='';
- mapList[i].forEach(function (item) {
- strLine=strLine+' , '+item.toString();
- });
- console.log(strLine);
- }
- mapList=[];
- }
- async function getLatticeValue(){
- let optionD = {
- uri: '',
- method: 'GET',
- headers: {
- 'Accept': '*/*',
- 'Accept-Language': 'zh-CN,zh;q=0.9',
- 'Connection': 'keep-alive',
- 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
- 'Host': 'materialsproject.org',
- 'Referer': 'https://materialsproject.org/materials/mp-1434/',
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
- },
- json:true
- };
- for(let i=0;i<mapList.length;i++){
- optionD.uri='https://materialsproject.org/materials/'+mapList[i].get("materials_id")+'/structure';
- //console.log(optionD.uri);
- await rp(optionD)
- .then(function (res) {
- if(res.exp_lattice!==null){
- mapList[i].set('latticeFrom','ICSD');
- mapList[i].set('a',res.exp_lattice.a);
- mapList[i].set('b',res.exp_lattice.b);
- mapList[i].set('c',res.exp_lattice.c);
- mapList[i].set('alpha',res.exp_lattice.alpha);
- mapList[i].set('beta',res.exp_lattice.beta);
- mapList[i].set('gamma',res.exp_lattice.gamma);
- }else {
- mapList[i].set('latticeFrom','computed');
- mapList[i].set('a',res.structure.lattice.a);
- mapList[i].set('b',res.structure.lattice.b);
- mapList[i].set('c',res.structure.lattice.c);
- mapList[i].set('alpha',res.structure.lattice.alpha);
- mapList[i].set('beta',res.structure.lattice.beta);
- mapList[i].set('gamma',res.structure.lattice.gamma);
- }
- }).catch(function (err) {
- console.log(err);
- });
- }
- }
运行结果
如下所示,数据从左到右依次为:材料id、分子式、空间群符号、禁带宽度、数据来源,以及点阵常数的a、b、c、alpha、beta、gamma。
程序运行正常,打印所有分子式后把数据粘贴到excel交付给师兄就好了。。。
实验中遇到的问题以及解决方法
- Q1:请求uri后,获取的内容为乱码?
- A1:不让代码被压缩,在option中因为添加了此句:Accept-Encoding: gzip, deflate, br,则响应的内容就会被压缩,而在内容处理中又没有引用解码的模块,所以会乱码,因此删除该句便可
- Q2:json中key含有空格,该如何处理?
- A2:在请求到的第一个json文件中,需要获得value的禁带宽度,其key含有空格,在访问时这样写['key']便可以,具体看这里