爪巴虫

基础概念

什么是爬虫

网络爬虫,是一种按照一定规则,自动抓取互联网信息的程序或脚本。由于互联网数据的多样性和资源的有限性,根据用户需求定向抓取相关网页并分析已成为如今主流的爬取策略

爬虫的本质

模拟浏览器打开网页,获取网页中我们想要的那部分数据

原理图

image-20201212142838572

豆瓣TOP250

需要引入的包

from bs4 import BeautifulSoup #网页解析,获取数据
import re #正则表达式,进行文字匹配
import urllib #指定URL,获取网页数据
import xlwt #进行excel操作
import sqlite3 #进行SQLite数据库操作

构建流程

准备工作

分析网页后,做准备工作

baseurl = "https://movie.douban.com/top250?start="
datalist=getData(baseurl)
savepath=".\\豆瓣电影Top250.xls"
saveData(datalist,savepath)

爬取网页

用urllib库获取页面

  • 对每一个页面,调用askURL函数获取页面内容
  • 定义一个获取页面的函数askURL,传入一个url参数,表示网址
  • urllib.Request生成请求;urllib.urlopen发送请求获取响应;read获取页面内容
  • 在访问页面时经常会出现错误,为了程序正常运行,加入异常捕获语句

解析数据

关于BeautifulSoup4

BeautifulSoup4将复杂的HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为以下4种

  • Tag
  • NavigableString
  • BeautifulSoup
  • Comment

引包

from bs4 import BeautifulSoup

解析内容

bs = BeautifulSoup(html,"html.parser")
#页面内容,解析器
类型对象

Tag类型 标签及其内容:拿到它所找到的第一个内容

print(bs.title)

NavigableString类型 标签里的内容(字符串)

print(bs.title.string)#字符串
print(bs.div.attrs)#键值对类型的标签属性

BeautifulSoup类型 解析后的类型,即bs自身,表示整个文档

Comment类型,是一个特殊的NavigableString,输出的内容不包含注释,遇到注释时自动生成这个类型

文档的遍历
print(bs.head.contents)

contents可以将tag的子节点以列表方式输出,也可以用索引来获取对应元素

文档的搜索

find_all方法,查找所有,会返回所有与字符串完全匹配的标签名的内容

t_list = bs.find_all("a")

re.compile方法,正则表达式

t_list = bs.find_all(re.compile("^a$"))

传入一个函数(方法),根据函数的要求来搜索

    # def name_is_exists(tag):
    #     return tag.has_attr("name")
    # t_list = bs.find_all(name_is_exists)

kwargs 参数,向find_all中传入kwargs参数,寻找对应标签

t_list = bs.find_all(id="head")#搜索对应属性内容的
t_list = bs.find_all(class_=True)#搜索定义对应属性与否的

text参数

用于查找字符内容,支持传入数组类型,也可以使用正则

t_list = bs.find_all(text="12306")
t_list = bs.find_all(text= re.compile("\d"))

limit参数

在查找时和目标一同传入,限制条数

t_list = bs.find_all("a",limit=3)

css选择器

t_list = bs.select('title')    #通过标签查找
t_list = bs.select('.box') #通过类名查找
t_list = bs.select('#box') #通过id查找
t_list = bs.select("a[class='bri']") 通过属性查找
t_list = bs.select(”head > title“) 通过子标签查找
t_list = bs.select(".mnav ~ .bri") 通过兄弟节点查找,和class为mnav的标签为兄弟的class为bri的标签
关于Re库(正则表达式)

image-20201212204304984

模式限定

正则表达式可以包含一些可选标志修饰符来控制匹配的模式。修饰符被指定为一个可选的标志。多个标志可以通过按位OR(|)它们来指定。如re.I|re.M被设置成I和M标志:

image-20201212204619649

#正则表达式:字符串模式(判断字符串是否符合一定的标准)

import re
# 创建模式对象

# pat = re.compile("AA") # 此处的AA,是正则表达式,用来验证其他字符串
# res = pat.search("BAAB") # search字符串是被校验的内容,search只会找到第一个符合或不符的地方
# print(res)

# 没有模式对象(模板,被校验对象)
# m = re.search("asd","Aasd")

# print(re.findall("a","ASDaDFGAa"))# findall会查找所有符合规则的子串
# print(re.findall("[A-Z]","ASDIJdsinGNOIX"))
# print(re.findall("[A-Z]+","ASDIJdsinGNOIX"))# 加号,一个到无数个的组合

# sub
# print(re.sub("a","A","abckjlaeioqwr"))# 找到a,用A来替换
# 建议在正则表达式中,被比较的字符串前面加上r(解决转义问题),不用担心转义字符的问题
解析内容

对爬取的html文件进行解析

  • 使用BeautifulSoup定位特定的标签位置
  • 使用正则表达式找到具体的内容

image-20201213195133499

爬取源码,使用解析器解析,查找符合要求的字符串,形成列表

for i in range(0,10): #调用获取页面信息的函数10次(处理分页)
    url = baseurl + str(i*25)
    html = askURL(url) #存储获取到的网页源码

    soup = BeautifulSoup(html,"html.parser")
        for item in soup.find_all('div',class_="item"): #分析特征,查找符合要求的字符串,形成列表
            # print(item) #测试,查看电影item全部信息
            data = [] #保存一部电影的所有信息
            item = str(item)

            link = re.findall(findLink,item)[0] #re库用来通过正则表达式查找指定的字符串
            print(link)

定义在全局中的正则

#影片详情页链接规则
findLink = re.compile(r'<a href="(.*?)">') #分析目标数据,创建正则表达式对象,表示规则(字符串的模式)

执行以上代码,即可得到页面内的所有详情页链接

之后,对图片、片名、评分、评分人数、概述做同样操作

正则:

# 影片图片的链接
findImgSrc = re.compile(r'<img.*src="(.*?)"',re.S)# re.S,让换行符包含在字符中
# 影片片名
findTitle = re.compile(r'<span class="title">(.*)</span>')# 有结尾时不需要问号来表示懒惰规则
# 影片评分
findRating = re.compile(r'<span class="rating_num" property="v:average">(.*)</span>')
# 找到评价人数
findJudge = re.compile(r'<span>(\d*)人评价</span>')
# 找到概况
findInq = re.compile(r'<span class="inq">(.*)</span>')
# 找到影片的相关内容
findBD = re.compile(r'<p class="">(.*)</p>',re.S)

添加进data里,然后加入datalist:

        soup = BeautifulSoup(html,"html.parser")
        for item in soup.find_all('div',class_="item"): #分析特征,查找符合要求的字符串,形成列表
            # print(item) #测试,查看电影item全部信息
            data = [] #保存一部电影的所有信息
            item = str(item)
            link = re.findall(findLink,item)[0] #re库用来通过正则表达式查找指定的字符串
            data.append(link)   #添加详情页链接
            imgSrc = re.findall(findImgSrc,item)[0]
            data.append(imgSrc)     #添加图片
            titles = re.findall(findTitle,item)  #片名可能只有一个中文名,没有外文名
            if(len(titles) == 2):
                titles_zh = titles[0]
                data.append(titles_zh)  #添加中文名
                titles_fo = titles[1].replace("/","")#对外文名做替换,去掉无关的符号
                data.append(titles_fo)  #添加外文名
            else:
                data.append(titles[0])
                data.append(" ")  #留空,以便后期数据处理
            rating = re.findall(findRating,item)[0]
            data.append(rating)     #添加评分

            judgeNum = re.findall(findJudge,item)[0]
            data.append(judgeNum)   #添加评价人数

            inq = re.findall(findInq,item)
            if len(inq) != 0:
                inq = inq[0].replace("。","")    #去掉句号
                data.append(inq)    #添加概述
            else:
                data.append(" ")    #留空

            bd = re.findall(findBD,item)[0]
            bd = re.sub("<br(\s+)?/>(\s+)?"," ",bd)     #去掉<br/>
            bd = re.sub('/',"",bd)      #替换/
            data.append(bd.strip())     #去掉前后的空格然后添加概述

            datalist.append(data)

保存数据

关于xwlt

xwlt是用于excel操作的模组,方便我们将爬取的数据导入到excel文件中

引包

import xwlt

基础操作

workboox()方法,创建workbook对象

add_sheet()方法,创建工作表

writ(行,列,内容)方法,写入内容

sava()方法,保存workbook

workbook = xlwt.Workbook(encoding="utf-8")  #创建workbook对象
worksheet = workbook.add_sheet('sheet1')    #创建工作表
worksheet.write(0,0,'hello')    #写入内容,(行,列,参数)
workbook.save('student.xls')    #保存数据表

打印九九乘法表的练习

import xlwt

workbook = xlwt.Workbook(encoding="utf-8")  #创建workbook对象
worksheet = workbook.add_sheet('sheet2')    #创建工作表
i = 0
j = 0
for i in range(0,9):
    for j in range(0,i+1):
        worksheet.write(i,j,"%d * %d = %d"%(j+1,i+1,(j+1)*(i+1)))
workbook.save('student2.xls')    #保存数据表
excel表存储

image-20201213195251956

根据数据分为8列,先写入每列列名,然后循环写入数据即可

def saveData(datalist,savepath):
    workbook = xlwt.Workbook(encoding="utf-8",style_compression=0)      #样式压缩
    worksheet = workbook.add_sheet('豆瓣电影Top250',cell_overwrite_ok=True)     #可覆盖
    col = ("电影详情链接", "图片链接", "影片中文名", "影片外文名", "评分", "评价人数", "概况", "相关信息")
    for i in range(0,8):
        worksheet.write(0,i,col[i])     #写入列名
    print("=============写入中=============")
    for i in range(0,250):
        print("正在写入第%d条"%(i+1))
        data = datalist[i]
        for j in range(0,8):
            worksheet.write(i+1,j,data[j])

    workbook.save(savepath)
关于SQLite数据库
import sqlite3

sqlite3是一个非常轻量级的数据库,适合进行练习,后续可以换用mysql或者oracle

基础操作

import sqlite3

# 连接数据库
conn = sqlite3.connect("test.db" )      #打开或创建数据库文件

print("成功打开数据库")

# 建立数据表

# c = conn.cursor()   #获取游标
#
# sql = '''
#     CREATE TABLE company
#         (id int primary key not null,
#         name text not null,
#         age int not null,
#         address char(50),
#         salary read);
#
#
# '''
#
# c.execute(sql)   #执行sql语句
# conn.commit()    #提交数据库操作
# conn.close()     #关闭数据库连接
#
# print("成功建表")

# 插入数据
# c = conn.cursor()   #获取游标
#
# sql1 = '''
#     INSERT into company (id,name,age,address,salary)
#      values (2,'张三',32,"成都",8000);
#
#
# '''
# sql2 = '''
#     INSERT into company (id,name,age,address,salary)
#      values (3,'李四',30,"重庆",15000);
#
#
# '''
#
# c.execute(sql1)   #执行sql语句
# c.execute(sql2)
# conn.commit()    #提交数据库操作
# conn.close()     #关闭数据库连接
#
# print("成功插入")

# 查询数据
c = conn.cursor()   #获取游标

sql1 = "SELECT id,name,address,salary from company"

cursor = c.execute(sql1)   #执行sql语句

for row in cursor:
    print("id = ",row[0])
    print("name = ",row[1])
    print("address = ",row[2])
    print("salary = ",row[3])

conn.commit()    #提交数据库操作
conn.close()     #关闭数据库连接

print("查询完毕")

所以……我选择mysql

引包

import mysql.connector

连接数据库

    mydb = mysql.connector.connect(
        host="yourhost",
        user="youruser",
        passwd="yourpassword",
        port=yourport,
        database="spider"
    )

建库建表

    mycursor = mydb.cursor()
    # 建库
    mycursor.execute("CREATE DATABASE spider")
    # print(mydb)
    # 建表
    mycursor.execute('''CREATE TABLE movie250
                        (id INTEGER(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
                         cname VARCHAR(100),
                         ename VARCHAR(100),
                         info VARCHAR(255),
                         info_link VARCHAR(255),
                         instroduction VARCHAR(255),
                         pic_link VARCHAR(255),
                         rated INTEGER(50),
                         score FLOAT(20))''')

数据的插入

将datalist里的数据循环出来,然后按照数据库语句规则合理拼接即可

def saveData2DB(datalist,dbname):
    mydb = mysql.connector.connect(
        host="cdb-mg417xta.cd.tencentcdb.com",
        user="root",
        passwd="komeiji_koishi",
        port=10079,
        database=dbname
    )

    cur = mydb.cursor()

    for (indexdata,data) in datalist:
        for index in range(len(data)):
            data[index] = '"'+data[index]+'"'
        sql = '''
            insert into movie250 (
            info_link,pic_link,cname,ename,score,rated,instroduction,info)
            values(%s)'''%",".join(data)    #%",".join(data),把data里面的数据都用逗号连接起来
        # print(sql)
        # print("正在将第%d页注入数据库"%indexdata)
        cur.execute(sql)
        mydb.commit()
    cur.close()
    mydb.close()

然后做一下数据可视化即可

如果有空可以学一下pythonweb

最后附上完整代码

#coding=utf-8
from bs4 import BeautifulSoup
import re
import time
import random
import urllib
import urllib.request
import xlwt
import mysql.connector
import sqlite3

# 影片详情页链接规则
findLink = re.compile(r'<a href="(.*?)">') #分析目标数据,创建正则表达式对象,表示规则(字符串的模式)
# 影片图片的链接
findImgSrc = re.compile(r'<img.*src="(.*?)"',re.S)# re.S,让换行符包含在字符中
# 影片片名
findTitle = re.compile(r'<span class="title">(.*)</span>')# 有结尾时不需要问号来表示懒惰规则
# 影片评分
findRating = re.compile(r'<span class="rating_num" property="v:average">(.*)</span>')
# 找到评价人数
findJudge = re.compile(r'<span>(\d*)人评价</span>')
# 找到概况
findInq = re.compile(r'<span class="inq">(.*)</span>')
# 找到影片的相关内容
findBD = re.compile(r'<p class="">(.*?)</p>',re.S)


# main函数
def main():
    baseurl = "https://movie.douban.com/top250?start="
    # 1.爬取网页
    datalist = getData(baseurl)


    # 3.保存数据
    # savepath = ".\\豆瓣电影Top250.xls"    #excel输出
    # saveData(datalist,savepath)
    dbname = "spider"     #数据库输出
    saveData2DB(datalist,dbname)



# 保存数据到excel
def saveData(datalist,savepath):
    workbook = xlwt.Workbook(encoding="utf-8",style_compression=0)      #样式压缩
    worksheet = workbook.add_sheet('豆瓣电影Top250',cell_overwrite_ok=True)     #可覆盖
    col = ("电影详情链接", "图片链接", "影片中文名", "影片外文名", "评分", "评价人数", "概况", "相关信息")
    for i in range(0,8):
        worksheet.write(0,i,col[i])     #写入列名
    print("=============写入中=============")
    for i in range(0,250):
        print("正在写入第%d条"%(i+1))
        data = datalist[i]
        for j in range(0,8):
            worksheet.write(i+1,j,data[j])

    workbook.save(savepath)



def saveData2DB(datalist,dbname):
    mydb = mysql.connector.connect(
        host="",
        user="",
        passwd="",
        port=10079,
        database=dbname
    )

    cur = mydb.cursor()

    for (indexdata,data) in datalist:
        for index in range(len(data)):
            data[index] = '"'+data[index]+'"'
        sql = '''
            insert into movie250 (
            info_link,pic_link,cname,ename,score,rated,instroduction,info)
            values(%s)'''%",".join(data)    #%",".join(data),把data里面的数据都用逗号连接起来
        # print(sql)
        # print("正在将第%d页注入数据库"%indexdata)
        cur.execute(sql)
        mydb.commit()
    cur.close()
    mydb.close()






#创建数据表
def init_db():
    print("运行了初始化操作")
    mydb = mysql.connector.connect(
        host="",
        user="",
        passwd="",
        port=,
        database="spider"
    )

    # mycursor = mydb.cursor()
    # # 建库
    # mycursor.execute("CREATE DATABASE spider")
    # # print(mydb)
    # # 建表
    # mycursor.execute('''CREATE TABLE movie250
    #                     (id INTEGER(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
    #                      cname VARCHAR(100),
    #                      ename VARCHAR(100),
    #                      info VARCHAR(255),
    #                      info_link VARCHAR(255),
    #                      instroduction VARCHAR(255),
    #                      pic_link VARCHAR(255),
    #                      rated INTEGER(50),
    #                      score FLOAT(20))''')
    # mycursor.execute("SHOW TABLES")
    # for x in mycursor:
    #     print(x)



# 爬取网页
def getData(baseurl):
    datalist = []
    print("=============爬取中=============")
    for i in range(0,10): #调用获取页面信息的函数10次(处理分页)
        print("礼貌爬取,爬虫正在休眠,3秒后继续爬取下一页")
        url = baseurl + str(i*25)
        time.sleep(3)
        print("正在爬取第%d页"%(i+1))
        html = askURL(url) #存储获取到的网页源码
    # 2.逐一解析数据
        #选择内容和解析器
        soup = BeautifulSoup(html,"html.parser")
        for item in soup.find_all('div',class_="item"): #分析特征,查找符合要求的字符串,形成列表
            # print(item) #测试,查看电影item全部信息
            data = [] #保存一部电影的所有信息
            item = str(item)
            link = re.findall(findLink,item)[0] #re库用来通过正则表达式查找指定的字符串
            data.append(link)   #添加详情页链接
            imgSrc = re.findall(findImgSrc,item)[0]
            data.append(imgSrc)     #添加图片
            titles = re.findall(findTitle,item)  #片名可能只有一个中文名,没有外文名
            if(len(titles) == 2):
                titles_zh = titles[0]
                data.append(titles_zh)  #添加中文名
                titles_fo = titles[1].replace("/","")#对外文名做替换,去掉无关的符号
                data.append(titles_fo)  #添加外文名
            else:
                data.append(titles[0])
                data.append(" ")  #留空,以便后期数据处理
            rating = re.findall(findRating,item)[0]
            data.append(rating)     #添加评分

            judgeNum = re.findall(findJudge,item)[0]
            data.append(judgeNum)   #添加评价人数

            inq = re.findall(findInq,item)
            if len(inq) != 0:
                inq = inq[0].replace("。","")    #去掉句号
                data.append(inq)    #添加概述
            else:
                data.append(" ")    #留空

            bd = re.findall(findBD,item)[0]
            bd = re.sub("<br(\s+)?/>(\s+)?"," ",bd)     #去掉<br/>
            bd = re.sub('/',"",bd)      #替换/
            data.append(bd.strip())     #去掉前后的空格然后添加概述

            datalist.append(data)
    # print(datalist)
    return datalist



#获取指定一个URL的网页内容
def askURL(url):

    head = {    #模拟浏览器头部信息,向服务器发送信息,注意直接复制过来的时候,键值对会被自动换行和加空格,需要去掉
        "User-Agent": "Mozilla/5.0(Windows NT 10.0;WOW64)AppleWebKit/537.36(KHTML, likeGecko)Chrome/72.0.3626.81Safari/537.36SE2.XMetaSr1.0"
    }    # 用户代理,用来伪装爬虫,让服务器端将请求识别为浏览器(本质上是告诉服务器我们可以接收什么样的内容)
    #将头部信息和url整合成一个request对象,以便url携带浏览器头访问
    request = urllib.request.Request(url,headers=head)
    html = ""
    #接受可能出错,所以加上异常处理
    try:
        response = urllib.request.urlopen(request)
        html = response.read().decode("utf-8")
        # print(html)
    except urllib.error.URLError as e:
        if hasattr(e,"code"):
            print(e.code)
        if hasattr(e,"reason"):
            print(e.reason)
    return html



if __name__ == '__main__':#当程序执行时调用函数
    main()
    # init_db() # 创建完记得注释掉,这里建议去数据库创建
    print("爬取完毕")

正则表达式的扩展

正则表达式常用操作符

image-20201212203131377

示例 126邮箱

.*@126.com

image-20201212203837183

礼貌爬取

对网站的访问是会带来服务器压力的,利用睡眠方法减缓速度,降低给对方网站带来的负担是学习爬虫时的道德和礼仪,过快爬取可能会被ip封禁

睡眠方法:

import time
time.sleep(3)    #里面是秒数,对于个人学习来说,3秒是一个合适的时长,一般网站对爬虫的限制不会超过这个秒数
Last modification:December 13th, 2020 at 11:01 pm