爬虫入门 – Selenium

当我们需要大量获取网上的信息时,手动一个个获取必然会浪费大量的时间和精力,这时我们可以按照一定的规则编写程序,帮助我们收集信息,这就是爬虫。

网上的爬虫工具不可胜数,对于前端开发者来说Selenium是个不错的选择:支持多种编程语言(C#JavascriptJavaPythonRuby),并且是直接在浏览器中运行,就跟真实的用户一样。

安装起来十分简单:

npm install selenium-webdriver

下载和自己浏览器版本对应的组件,并放到项目根目录下:


编写爬虫程序需要有四个步骤:

  1. 明确目标
  2. 业务分析
  3. 收集信息
  4. 储存数据

一、明确目标

以爬虫的“Hello World”,豆瓣Top250为例,抓取每部电影的名称、年份、评分、演员等信息。

二、业务分析

250部电影共分为10页显示,每页显示25部电影,参数start决定从第几部开始显示。我们需要先获取到每部电影的内容页面链接,然后访问该页面获取里面的信息,根据该需求我们不难写出下面两个函数:

/**
 * 在电影列表页面获取每部电影内容页面链接
 * @returns String[] 链接数组
 */
const getMovieLinks = () => Array.from(document.querySelectorAll('.grid_view .info a')).map(link => link.href)
const getData = () => {
  const $rating = document.querySelector('#interest_sectl')
  return {
    'name': document.querySelector('[property="v:itemreviewed"]').innerText,
    'year': document.querySelector('.year').innerText.substr(1, 4),
    'rating': {
      'average': $rating.querySelector('[property="v:average"]').innerText,
      'count': $rating.querySelector('[property="v:votes"]').innerText,
      'weights': Array.from($rating.querySelectorAll('.rating_per')).map((rating) => rating.innerText)
    },
    'director': document.querySelector('#info span:first-child .attrs').innerText,
    'leading': Array.from(document.querySelectorAll('#celebrities .celebrity a.name')).map((leading) => leading.innerText),
    'categories': document.querySelector('#info [property="v:genre"]').innerText,
  }
}

有了这两个核心函数,剩下的工作就只剩下往对应的页面里执行函数了。

三、收集信息

const { Builder } = require('selenium-webdriver')

const driver = new Builder().forBrowser('chrome').build()

const getMovieLinks = // 上面定义的函数
const getData = // 上面定义的函数

;(async () => {
  /**
   * 访问电影列表页面,在里面执行getMovieLinks函数获取每部电影内容页面的链接
   */
  const links = [];
  for (let i = 0; i < 250; i += 25) {
    await driver.get(`https://movie.douban.com/top250?start=${i}`)
    links.push(...await driver.executeScript(getMovieLinks))
    console.log(`getting links ${i}`)
  }
  /**
   * 分别访问上面获取到的链接,执行getData获取电影信息
   */
  const movies = [];
  for (let i = 0, len = links.length; i < len; i ++) {
    await driver.get(links[i])
    const movie = await driver.executeScript(getData)
    movie.rank = i + 1
    movies.push(movie)
    console.log(movie)
  }
  // 退出浏览器
  await driver.quit()
})()

理论上来说,上面的代码已经没什么问题了,应该能收集到所有电影的信息,尝试执行:

每次执行都会在不同的页面出现报错,当把getData函数放到该页面的控制台执行时却又不会出现,因此该错误可能是因为页面未加载就执行代码引起的,所以可以稍微升级一下getData函数:

const getMovieData = (callback) => {
  let finished = false
  const getData = () => {
    if (finished) return
    const $rating = document.querySelector('#interest_sectl')
    // onload和setTimeout是异步执行的,要用callback替换return实现返回
    callback({
      'name': document.querySelector('[property="v:itemreviewed"]').innerText,
      'year': document.querySelector('.year').innerText.substr(1, 4),
      'rating': {
        'average': $rating.querySelector('[property="v:average"]').innerText,
        'count': $rating.querySelector('[property="v:votes"]').innerText,
        'weights': Array.from($rating.querySelectorAll('.rating_per')).map((rating) => rating.innerText)
      },
      'director': document.querySelector('#info span:first-child .attrs').innerText,
      'leading': Array.from(document.querySelectorAll('#celebrities .celebrity a.name')).map((leading) => leading.innerText),
      'categories': document.querySelector('#info [property="v:genre"]').innerText,
    })
    finished = true
  }
  // 处理页面未加载完成就执行getData引起的报错
  try {
    getData()
  } catch (e) {
    onload = getData
  }
  // 极小几率会出现上面代码不执行的情况,延迟两秒执行保底
  setTimeout(getData, 2000)
}

前面的代码也需要升级为:

;(async () => {
  // ...
  const movies = [];
  for (let i = 0, len = links.length; i < len; i ++) {
    // ...
    const movie = await driver.executeAsyncScript(getMovieData)
    // ...
  }
  // ...
})()

再重新执行,问题已经得到解决。

四、储存数据

movies数组中已经包含了所有电影的信息,可以根据自身需求进行储存,这里演示另存为json文件的做法:

const { Builder } = require('selenium-webdriver')
const fs = require('fs')

// ...

;(async ()=> {
  // ...
  fs.writeFileSync('top250.json', JSON.stringify(movies))
  await driver.quit()
})()

最后

当然真正的爬虫需求当然不会像本文一样简单,一般公司级的网站都有做反爬虫处理,反反爬虫才是爬虫岗位中最棘手的工作。

留下评论

邮箱地址不会被公开。 必填项已用*标注

给博主打赏

2元 5元 10元