阅读 351

都9102年了,你还只会用python爬虫吗?

前言

无聊的时候,我们总会打开豆瓣电影榜,从上到下刷着电影,仿佛那些经典台词马上就在我们耳边响起,如《肖申克的救赎》中:生活可以归结为一种简单的选择:不是忙于生存,就是赶着去死;《霸王别姬》中:说的是一辈子!差一年,一个月,一天,一个时辰,都不算一辈子!但是,豆瓣电影那么多,还有那么多页,一页一页的找岂不是太费时间了,今天我就来从前端的角度来爬取豆瓣电影榜top250的首页。豆瓣top250

正文

1. 获取豆瓣首页的html

由于我们需要向豆瓣的服务器发送一个请求来得到豆瓣首页的html代码,请求与响应在网络传输中需要一定的时间,而后续的操作需要在这个响应的基础上进行,因此,我们需要将主函数定义为异步的。以上实现需要引入下面两个包(注意:request 是对等依赖,需要与request-promise 分开安装)

    npm i request
    npm i request-promise  
复制代码
    let request = require('request-promise')
    const main = async() => {
      let html = await request({
        url'https://movie.douban.com/top250'
      })
      console.log(html)
    }
    main();
复制代码

2.对html结构分析

这样,我们就得到了豆瓣首页的html代码,但是我们需要的只是很少的一部分信息,所以我们要对其中的信息进行提取,因此我们引入一个npm包cheerio来提取html 特定选择器中的文本内容

    npm i cherrio  
复制代码

然后在第二行引入

    let cheerio = require('cheerio');
复制代码

接下来我们要做的就是利用cheerio 将得到的html文本载入内存构建DOM,并使用选择器对目标标签中的文本进行提取,因此我们需要分析豆瓣首页的html结构

content

content中是我们需要的内容,gird-16-8 用于设置主体内容的布局,article是电影项的主体部分,我们需要的内容就在其中,aside 置于网页右侧放置醒目信息,extra用于清除两端浮动。现在让我们进入article中看看


我们可以看到是一个典型的head,body,foot结构
opt mod 内放置一个复选框,用于用户选择根据条件筛选
grid_view 提取内容的主体
paginator 页尾的分页导航 进入grid_view,我们所需要的内容都放置在一个一个的列表中,列表中的每一个item都是一个我们需要提取的一部电影。

3.使用选择器得到需要的内容

根据上面我们对结构的分析,我们就可以使用下列语句得到我们选择器内的每一个item

let movieNodes = $('#content .article .grid_view').find('.item');
复制代码

然后我们对保存了所有item的这样一个数组遍历,将其中每一项的关键值提取出来并保存,选择器的构建方法与上述的item获得的方法一致

let movies = [];
for (let i = 0; i < movieNodes.length; i++) {
    let $ = cheerio.load(movieNodes[i]);
    let titles = $('.info .hd span');
    titles = ([]).map.call(titles, t => $(t).text());
    let bd = $('.info .bd');
    let info = bd.find('p').text();
    let score = bd.find('.star .rating_num').text();
    movies.push({
      'titles': titles,
      'info': info,
      'score': score
    })
  }
复制代码

不同的是,由于.hd 的选择器下面有四个span,因此我们得到的是一个对象数组,我们不能直接进行提取否则会出现对象循环引用的错误,我们先使用map对空数组的每一个元素进行映射,然后再通过改变this指针,使用选择器对空数组中每一个元素进行赋值。然后将提取出来的每一个字段append到数组中。

4.文件保存

接下来,我们在第三行加上

let fs = require('fs');
复制代码

在for循环的外面继续补充

fs.writeFile('./mainjs.json'JSON.stringify(movies), 'utf-8', (err) => {
    if (err)
      console.log('写入失败');
    else
      console.log('写入成功');
  });
复制代码

就可以将我们刚刚在存在数组中的数据以json格式写入文件中啦,打开这个文件就会发现豆瓣top250的首页内容就存到了这个文件中,我们就可以挑选自己喜欢的电影观看了。 现在,让我们回过头来看看豆瓣top250首页的页面结构

body豆瓣的首页由100%的body占据,body又分为上中下三部分,每个版块又按照一定的功能再划分为相应的子模块,直到划分为一个最小的模块为止,每一个模块占据网页的一定位置,实现某一个特定的功能,每个模块之间互不干扰。

总结

通过对豆瓣的爬取,我们知道了,如果一个网站的结构带有一定规律,并有良好的可读性,会让我们的爬取更加简单,相反,如果一个网站乱糟糟的,毫无章法可言,那么这样的网站简直就是灾难。因此,我们在今后的编程中,应当多看看一些大型网站的html代码,学习其中的结构设计和命名规范。然后我们可以使用BEM(Block Element Modifier)命名规范,对每一个模块使用合适的选择器,这样不仅提高了代码的可重用行,也利于网站的管理和维护。 暑假闲来无事看了点python视频,虽然已经忘得都差不多了,但是用了node爬取了一次,又让我又拾起的对python的热情,于是一番折腾,查了相关文档,终于给捣鼓出来了,毕竟,生命在于折腾嘛! 以下是python的爬虫方式,基本思路与上述爬虫方式类似,只是对数据处理的语法上有所差异:
需要安转requests和beautifulsoup4模块

pip install requests
pip install beautifulsoup4
复制代码
import requests
from bs4 import BeautifulSoup
import json
def gettext(x): 
    return x.get_text()
#设置请求头伪装
header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'}
# 先使用requests发送网络请求从而获取网页
html = requests.get('https://movie.douban.com/top250', headers=header)
# print(html.status_code)
# 传入html构建DOM
soup=BeautifulSoup(html.content,'html.parser')
movieNodes = soup.find('ol', attrs={'class': 'grid_view'})
movies=[]
for movie in movieNodes.find_all('li'):
    title=movie.find('div',attrs={'class':'hd'})
    titles=title.find_all('span')
    titles1=map(gettext,titles)
    tit=[]
    for ti in titles1:
      tit.append(ti)
    info=movie.find('p').get_text()
    score=movie.find('span',attrs={'class':'rating_num'}).get_text()
    movies.append({
      "titles": tit,
      "info": info,
      "score": score
    })
f = open("mainpy.json", "w", encoding='utf-8')
json.dump(movies, f, ensure_ascii=False)
f.close()
复制代码
  • 写的时候遇到了以下这些小问题
  1. 爬取豆瓣首页时,打印不出来html源码,查了一下才想起来是豆瓣做了反爬措施,需要伪装一下请求头信息。所以以后我们在爬取网站的时候可以先打印一下状态码status_code,如果显示418就说明被屏蔽了,显示200就说明请求成功。
  2. 使用find_all时,返回的是一个object数组,需要使用map方法提取出item 中每一个span的内容,然后添加到数组中去。map方法的使用可以查看相关的文档,里面有对每一个参数具体说明。
  3. 写文件的时候,报错说不能写入一个list,必须写入一个字符串,然后我将这个数组转成字符串写入文件,结果里面对转义字符没有进行转义,多了'\xa0','\n' 等,查了一下发现这个玩意儿'\xa0'竟然就是html中的'&nbsp',然后我就用replace把每一个这个字符给删掉,结果存是存进去了,文件全爆红色,对比了mainjs.json才发现,保存的文件mainpy.json不是一个Json类型的数据,然后又是一顿度娘,发现有个专门写json文件的api,json.dump(),然后我replace直接也不用写了,不过需要引入json库....

结束语

可能有时候写程序就是这样,出一个问题然后你就去解决这个问题,然后又出现另外的问题,你又得想怎么去解决新出现的问题,然后一直一直....结果发现直接使用一个api就好了...但是写程序注重的是过程,bug是我们编程道路上的补品,我们不能做一个apier,不是吗?

关注下面的标签,发现更多相似文章
评论