从0开始的python爬虫

发表于 2019-02-16  10 次阅读


文章目录

本文基于Python 3.7.2,使用Visual Studio Code编写,目标是对shiyanlou.com的课程页进行信息抓取,并解析出需要的信息,写入Excel.

目标

对下图中红线部分进行爬取并写入excel.

目标网页
成果excel

过程

对于这些数据,我先定义一下。

课程号我定义为id,名称=coursename,免费=isfree,学过=learned,关注=subscribed,评论=commented,实验数=labsum,作者=name.

首先安装第三方库

pip install beautifulsoup4
pip install requests
pip install xlwt

requests的作用是读取网页,beautifulsoup4的作用是对网页html分析提取,xlwt是写入excel相关。

由于编码不同,贸然使用中文可能会出现一些问题,我找了两种解决方案。一是在vscode设置中把编码换成UTF-8;二是在代码的第一行加入以下来告诉python这个文件的编码。

 # -*- coding:UTF-8 -*- 

随后引入三个先要使用的库

import requests
from bs4 import BeautifulSoup
import re

为啥是from bs4,因为beautifulsoup这个库有两个版本,分别是3和4,这里我使用了bs4,库说明文档中也建议使用bs4。

实验楼的第一节课链接是 https://www.shiyanlou.com/courses/1,我们首先使用requests来获取这个网页,这个过程我定义为html,并将获取到的html转换成文本,然后将文本传给beautifulsoup.

html = requests.get(url='https://www.shiyanlou.com/courses/1').text
bf = BeautifulSoup(html)

requests的相关功能可在官方中文文档中查看,beautifulsoup的链接也在下方。

http://docs.python-requests.org/zh_CN/latest/user/quickstart.html

https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html

明确思路,先找课程名字。

在 <li class="active">下我找到了标题

<a href="/courses/1">        Linux 基础入门(新版)        </a>

标题

于是我们使用beautifulsoup(以下简称bf)的find_all功能查找

coursename = bf.find_all('a',href="/courses/1") 

这样提取到的是下面这些东西

[<a href="/courses/1">
        Linux 基础入门(新版)
        </a>]

我只想要标题,这显然不是我想要的,好在bf里有个text的功能,它可以去掉html代码只保留中间的文字,课程名前后还有八个空格,还有回车,我们一起把它们用replace去掉。

coursename=coursename[0].text.replace('        ','').replace('\n','')

这样的coursename就只剩下 Linux 基础入门(新版)

下一步判断是否免费,这步我用的方法有点蠢,在 <h4 class="course-infobox-title"> 下有课程名称和价格,使用和coursename同样的方法转换为纯文字后,将oursename删掉,剩下的只有价格,然后再判断是否免费,代码如下。

price=bf.find_all('h4',class_="course-infobox-title")
        price=price[0].text.replace(coursename,'').replace("\n", "")
        isfree='否'
        if  price== '免费':
            isfree='是'

isfree先设定为否,如果price的结果为‘免费’,就输出‘是’,不然就还是‘否’,因为目标中我们只需要输出‘是’或者‘否’。

然后是判断课程学过的人,关注数和评论数量,这里用正则表达式操作一下

coursedetail=bf.find_all('div',class_="course-info-details")
result = re.search(r'([0-9]*) 人学过\s*([0-9]*)人关注\s*([0-9]*)人评论', coursedetail[0].text  )

用了search查找,r表示raw,就是不转义后面的字符,([0-9]*)表示任意0-9之间的数字,因为这个数字是不固定的。这样查找到的三个数字就被分在了group里面。这里有个问题需要解决一下,就是有的课没有人学过(为什么会没人学过啊,我怀疑是系统bug) ,没人学过的话正则就找不到第一个数据,后面导出的时候就会出bug,所以我在这里加了一个判断。

if result:
   learned =result.group(1) 
                    

subscribed=result.group(2)
             commented = result.group(3)

如果result有结果的话就赋值,否则就给一个NaN(代码中未给出,放下面再说)

然后是作者名字,同样findall就完事了。

name=bf.find_all('div',class_="name")
        name=name[0].text

还剩最后一个实验数量,这是卡了我最久的一个地方,不仅仅在前期,这个地方后期也出了两个bug。

我的思路是先用bf提出这一块,通过正则表达式判断「第几节」,然后把中间的数字放在list里面,输出的时候输出第 (-1) 个,也就是最后一个。首先出现的第一个问题是,有的课没有实验数目,这样就导致我取数字的时候报错,我这学了一下午的速成python并不知道怎么处理,就直接加了一个判断,这样问题勉强也解决了。第二个错误我在c中经常犯,这个list里面包含了上一次的数据没有清空,又加了一行让它初始化,这样实验数目就搞定了。

labs=bf.find_all('div',role="tabpanel",id="reports")
        labs=labs[0].text
        labsnum=[]
        pattern = re.compile(r'第([0-9]*)节')
        labsnum=pattern.findall(labs)
        if labsnum:
            None
        else:
            labsnum=[0]
        

需要的数据都好了,还有一些问题要处理。

第一个解决的问题是404,有的课被实验楼删掉了,不存在,通过观察,我发现404的课,他们的标题是固定一样的,我判断标题是不是指定的文字,符合的话就略过直接下一步。

  is404 = bf.title.text
    if  is404 == '实验楼 - 在线做实验,高效学编程':
        print(id,"NOT FOUND")
        sheet1.write(id,0,id)
        sheet1.write(id,1,'NOT FOUND')
        continue
    else:

else后面就是后续的判断,title也是bf的内置参数,可以直接获取网页的标题。sheet1是通过xlwt写入excel的,后面说。

然后就是写入excel了,通过xlwt还是很方便的,详细参数可以看帮助文件

import xlwt 
book=xlwt.Workbook(encoding='utf-8')
sheet1=book.add_sheet('sheet1',cell_overwrite_ok=True)
row0 = ["课程号","名称","免费","学过",'关注','评论','实验数目','作者']
for i in range(0,len(row0)):
    sheet1.write(0,i,row0[i])

循环将row0中的数据写入excel第一行,同样加上编码utf-8防止中文出现错误。

在上面的result中,如果result有结果,就将数据写入row1中供后面写入excel

row1 = [id,coursename,isfree,learned,subscribed,commented,labsnum[-1],name]
            for i in range(0,len(row1)):
              sheet1.write(id,i,row1[i])

如果result没有返回的话,就换成几个NaN

row1 = [id,coursename,isfree,'NaN','NaN','NaN',labsnum[-1],name]

最后再将row1中的数据写入excel

  for i in range(0,len(row1)):
                sheet1.write(id,i,row1[i])

单个页面的问题解决了,在一开始的html上加个循环解决自定义页面数量。

for id in range(1,1000):
    html = requests.get(url='https://www.shiyanlou.com/courses/'+str(id)).text

保存文件,我以当前的时间命名excel文件并且保存在自定义目录下。

import time
date=time.strftime('%Y.%m.%d',time.localtime(time.time()))
book.save('d:\\'+date+'.xls')

成果

过程截图

爬虫过程截图
成果excel
本站文章基于国际协议BY-NA-SA 4.0协议共享;
如未特殊说明,本站文章皆为原创文章,请规范转载。

0

废紙的意思是,没有用的东西。