前言:本人很菜,学习很泛。

由于参加数学建模的需要,在这个寒假期间小学了一下爬虫(Python学习),想着我记性这么差,还是得对这段时间的学习进行整理,以防忘记。

一、爬虫介绍

网络爬虫又称网络蜘蛛、网络机器人,是指按照某种规则在网络上爬取所需内容的脚本程序。每个网页通常包含其他网页的入口和大量信息,网络爬虫则是进入网页,定位获取所需内容。

爬虫可以划分为以下三步:

  1. 爬取网页
  2. 解析数据
  3. 保存数据

其中最重要的应该是解析数据这部分,因为这部分编写对应的代码来定位html内容,从而获取我们想要爬取的数据。爬取部分可以通过urllib模块进行获取网页的html代码。保存数据,主要有两种方法,一种是直接保存进Excel,另一种是更面向系统,保存进数据库。我学习的过程使用的是SQLite数据库引擎,小型,方便。特别是运用Pycharm中dataset工具包,使得数据库运用起来是十分的方便。


二、爬取网页

平常登入浏览器上网,最频繁使用的两种请求是get请求和post请求。get请求输入的数据是直接显示在url上,安全性不够。post请求数据是不显示在url上,安全性比较高。(就比如输入密码,是直接在网页上输入,而不是写入url中)

下面代码是get和post请求和返回响应

httpbin.org是一个专门用于爬虫测试的网站

get请求

import urllib.request
response = urllib.request.urlopen('http://httpbin.org/get')
print(response.read().decode('utf-8'))

#响应内容
{
  "args": {}, 
  "headers": {
    "Accept-Encoding": "identity", 
    "Host": "httpbin.org", 
    "User-Agent": "Python-urllib/3.9", 
    "X-Amzn-Trace-Id": "Root=1-61f3ef09-6616eeab295d5103700d9757"
  }, 
  "origin": "112.50.41.143", 
  "url": "http://httpbin.org/get"
}

post请求

data = bytes(urllib.parse.urlencode({"hellow":"world"}), encoding='utf-8')
response = urllib.request.urlopen('http://httpbin.org/post', data=data)
print(response.read().decode('utf-8'))

#响应内容
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "hellow": "world"
  }, 
  "headers": {
    "Accept-Encoding": "identity", 
    "Content-Length": "12", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "Python-urllib/3.9", 
    "X-Amzn-Trace-Id": "Root=1-61f3f1af-3c727e890ee174572c7b86fd"
  }, 
  "json": null, 
  "origin": "112.50.41.143", 
  "url": "http://httpbin.org/post"
}

从上面的响应headers中,可以发现User-Agent的内容是Python-urllib/3.9,说明服务器端知道我们访问的环境,也就是说服务器知道我们在爬虫,对于一些设有安全性的网页会拒绝我们访问。

假如我们通过上述方式正常访问豆瓣网页,会发现出现了如下418警告。

response = urllib.request.urlopen(r'https://movie.douban.com')
print(response.read().decode('utf-8'))

#警告
urllib.error.HTTPError: HTTP Error 418:

那么是不是说明设有安全性的网页就没法访问了,显然,大佬们如此智慧,肯定是有办法解决的。

通过代码伪装浏览器访问头,成功爬取。消息头headers的内容可以通过打开浏览器按F12键,随便点击一个get或post请求,查看消息头即可。以下以火狐浏览器截图为例:

Python网络爬虫开发技术 python网络爬虫指南_网络爬虫

伪装浏览器头消息代码如下:

url = r'https://movie.douban.com'
headers = {
    'User-Agent': r'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.9 Safari/537.36',
    'Cookie': 'bid=7mM480uOFCo; dbcl2="252912187:UtqLjbzdBdY"; ck=6DJI; _pk_ref.100001.4cf6=["","",1642433569,"https://accounts.douban.com/"]; _pk_ses.100001.4cf6=*; __utma=30149280.116081932.1642433569.1642433569.1642433569.1; __utmc=30149280; __utmz=30149280.1642433569.1.1.utmcsr=accounts.douban.com|utmccn=(referral)|utmcmd=referral|utmcct=/; __utmz=223695111.1642433569.1.1.utmcsr=accounts.douban.com|utmccn=(referral)|utmcmd=referral|utmcct=/; __utmc=223695111; __utmb=223695111.0.10.1642433569; __utma=223695111.1563560722.1642433569.1642433569.1642433569.1; __gads=ID=c847fa157ddd2521-2202472700d0001e:T=1642433572:RT=1642433572:S=ALNI_Ma_fS0aJS2enPjG0sgam_fOP8ZAfA; push_doumail_num=0; push_noty_num=0; __utmt=1; __utmv=30149280.25291; __utmb=30149280.2.10.1642433569; _pk_id.100001.4cf6=b998428814f5d240.1642433569.1.1642434417.1642433569.; Hm_lvt_eaa57ca47dacb4ad4f5a257001a3457c=1642433569,1642433596,1642434417; Hm_lpvt_eaa57ca47dacb4ad4f5a257001a3457c=1642434417'
}

req = urllib.request.Request(url=url, headers=headers)
response = urllib.request.urlopen(req)
print(response.read().decode('utf-8'))

返回的html部分代码如下:

Python网络爬虫开发技术 python网络爬虫指南_python_02

接着,我们对返回的html进行处理,将我们想要爬取的内容整理提取出来。

三、解析数据

对数据进行解析提取,主要使用python的bs4库和正则表达式re库,bs4中的BeautifulSoup类封装处理html,方便定位到对应标签。

BeautifulSoup中最常用的.find_all(),可用于字符串、正则表达式、函数匹配。
将baidu.html存入目录下,并以baidu.html为检索目标,代码展示了各种定位方法,以实用为主。

BeatifulSoup

#各种提取数据的方式,《最好自己每个都试一下》,按需选择方法
from bs4 import BeautifulSoup

file = open('./baidu.html', 'rb')
html = file.read()

bs = BeautifulSoup(html, 'html.parser')

file = open('./baidu.html', 'rb')
html = file.read()

bs = BeautifulSoup(html, 'html.parser')

print(bs)               #html内容
print(bs.title)         #title内容
print(bs.a)             #标签a内容
print(bs.head)          #head内容
print(type(bs.head))    #head内容类型类
print(bs.title.string)  #只取title内容中标题部分
print(bs.a.attrs)       #标签a属性内容

# 1.Tab 标签及其内容,第一个
# 2.NavigableString 标签里的内容
# 3.BeautifulSoup 整个文档
# 4.Comment 是一个特殊的NavigableString 输出内容不包括注释
print(type(bs))
print(bs.name)
print(bs.attrs)
print(bs)

# -----------------------------------------
# 文档的遍历
print(bs.head.contents[1])

# 文档的搜索
# (1)find_all
# 字符串过滤: 查找与字符串完全匹配的内容
t_list = bs.find_all('a')
print(t_list)

# 正则表达式搜素: 使用search()方法匹配内容
t_list = bs.find_all(re.compile('a'))
print(t_list)

# 方法: 传入一个函数,根据函数要求搜索 (了解)
def name_is_exists(tag):
    return tag.has_attr('name')

t_list = bs.find_all(name_is_exists)
print(t_list)

# (2)kwargs 参数
t_list = bs.find_all(id = "head")
t_list = bs.find_all(class_=True)


# (3)text参数
t_list = bs.find_all(text='hao123')
t_list = bs.find_all(text=['hao123', '地图', '贴吧'])
t_list = bs.find_all(text=re.compile('\d')) #正则

# (4)limit参数
t_list = bs.find_all('a', limit=3)

# css选择器
t_list = bs.select('title') #标签
t_list = bs.select('.mnav')   #类名
t_list = bs.select('#u1')   #id
t_list = bs.select("a[class='bri']")   #属性
t_list = bs.select("head > title")   #子标签
t_list = bs.select(".mnav ~ .bri") #

print(t_list[0].get_text())

baidu.html

<!DOCTYPE html>
<html>
<head>
    <meta content="IE=Edge" http-equiv="X-UA-Compatible"/>
    <meta content="always" name="referrer"/>
    <link href="https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css"rel="stylesheet" type ="text/css" />
<!--    具体显示的样式-->
    <title>百度一下,你就知道啦</title>
</head>
<body link="#0000cc">
    <div id="wrapper">
      <div id="head">
        <div class="head_wrapper">
          <div id="u1">
            <a class="mnav" href="http://news.baidu.com" name="tj_trnews">!--新闻-</a>
            <a class="mnav" href="http://news.baidu.com" name="tj_trnews">新闻</a>
            <a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123</a>
            <a class="mnav" href="http:/ /map.baidu.com" name="tj_trmap">地图</a>
            <a class="mnav" href="http://v.baidu.com" name="tj_trvideo">视频</a>
            <a class="mnav" href="http://tieba.baidu.com" name="tj_trtieba">贴吧</a>
            <a class="bri" href="//www.baidu.com/more/" name="tj_briicon" style="....">更多产品</a>
          </div>
        </div>
      </div>
  </div>
</body>
</html>

正则表达式主要用于定位那些比较泛型的内容,也是爬虫常常用到的,如提取网站邮箱,邮箱的后缀通常都一样,如QQ邮箱后缀为@qq.com,但是前部分都是各自的QQ号,所以可以利用正则表达式将所以QQ邮箱号码提取出来,非常实用。以下是我自认为比较常用的正则表达式方法,具体的网上很多:

字符

描述

实例

.

匹配除换行符 \n 之外的任何单字符

( )

分组标记,内部只能使用

操作符

*

前一个字符0次或无限次扩展

abc*表示ab、abc、abcc、abccc等

+

前一个字符一次或无限次扩展

abc+表示abc、abcc、abccc等

?

前一个字符0次或者1次扩展

abc?表示ab、abc

[ ]

字符集,对单个字符给出取值范围

[abc]表示a、b、c,[a-z]表示a到z单个字符

表达式左右任意一个

{m}

扩展前一个字符m次

ab{2}c表示abbc

{m,n}

扩展前一个字符m至n次

ab{1,2}c表示abc,abbc

^

匹配字符串开头

^abc表示abc且在一个字符串的开头

$

匹配字符串结尾

abc$表示abc且在一个字符串的结尾

\d

数字,等价于[0-9]

\w

单词字符,等价于[A-Za-z0-9_]

正则表达式在Python用到是re库,里面提供各自种API对数据进行处理,以下我举例了几个简单的例子。

import re

pat = re.compile('AA') #此时的AA是正则表达式
m = pat.search('AACDBAA') #search字符串被校验的内容
print(m) #输入结果<re.Match object; span=(1, 3), match='AA'>

m = re.search('asd','Aasd')
m = re.findall('a', 'ASDdadaSJJAa')
m = re.findall('[A-Z]+', 'ASDdaAdaSJJAa')
m = re.sub('a', 'A', 'adadfafs') #第一个被替换,第二个替换后,第三个对象
a = r'dafa\fafa/faf\fafaf\fasd\d\fx'
# print(m)

将正则表达式应用于html解析
如前面的baidu.html,假如我们需要类mnav的href内容,只需要如下代码,十分便利

import re
from bs4 import BeautifulSoup

file = open('./baidu.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, 'html.parser')
findhref = re.compile(r'<a class="mnav" href="(.*?)"')
result = re.findall(findhref, str(bs))
print(result)

四、保存数据

主要用到xlwt(excel)和sqlite3(database)库,下面只介绍sqlite3。

sqlite是一个轻型的数据库,创建十分方便,只需要几行代码。适用于数据量不多的项目,对于爬虫是足够了。

#数据库的创建和初始化
def init_db(dbpath):
    sql = '''
        create table headImages(
            标题 varchar(20),
            日期 datatime,
            图片链接 text
        )

    '''
    conn = sqlite3.connect(dbpath)
    cursor = conn.cursor()
    cursor.execute(sql)
    conn.commit()
    conn.close()

其实对于数据库的各个sql语句的执行,都是直接通过游标cursor()执行,一行代码解决,没有什么困难。难的就是需要把所有的各个方方面面整合在一起,对于大多数项目其实都是这样,对于学习单个内容其实都蛮简单。

#insert into将数据插入table即可
def saveData2DB(dataList, dbpath):
    init_db(dbpath)
    conn = sqlite3.connect(dbpath)
    cur = conn.cursor()

    for 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)
        # print(sql)
        cur.execute(sql)

    conn.commit()
    cur.close()
    conn.close()

实战

写了爬取头像的代码,并且根据自己喜好去爬。还没有做web可视化,做好就可以直接在网站上实时更新并选择自己喜欢的头像,有时间再做。爬取头像关键词[‘可爱’, ‘动漫’, ‘手绘’, ‘搞怪’],代码附excel保存版本。

代码可执行

# -- coding: utf-8 --
# @Author: zrs
# @Time: 2022-02-07 14:53
# @File: main.py

import re
import xlwt
import sqlite3
import numpy as np
import urllib.request
from bs4 import BeautifulSoup

url = r'https://www.woyaogexing.com/touxiang/qinglv/'

def askURL(url):
    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:96.0) Gecko/20100101 Firefox/96.0',
    'Cookie': 'BAIDU_SSP_lcr=https://www.baidu.com/link?url=pCdSJIVWq3oRa2SQ63p2b2G6MhUGFLwmqAr4tM9pP-HMoNZVYwtunks5FpRiI6X7&wd=&eqid=bc47740e0007c8de000000056200c13b; Hm_lvt_a077b6b44aeefe3829d03416d9cb4ec3=1643031884,1644216664,1644216690; __gads=ID=03cc11a055dcdbab-2294c11433d00095:T=1643031885:RT=1643031885:S=ALNI_MYbVQjpAjO2k9fH36n0Lkm_6gkiYA; Hm_lpvt_a077b6b44aeefe3829d03416d9cb4ec3=1644216740'
    }

    request = urllib.request.Request(url, headers=headers)
    html = ''

    try:
        response = urllib.request.urlopen(request)
        html = response.read().decode('utf-8')
    except urllib.error.URLError as e:
        if hasattr(e, 'code'):
            print(e.code)
        if hasattr(e, 'reason'):
            print(e.reason)

    return html

def getData(base_html):


    soup = BeautifulSoup(base_html, 'html.parser')
    # print(soup)
    findHref = re.compile(r'<a class="img" href="(.*?)"')
    findTime = re.compile(r'<div class="artTime y">(.*?)</div>')

    features = ['可爱', '动漫', '手绘', '搞怪']
    dataList = []
    for txList in soup.find_all('div', class_='txList'):
        title = txList.find_all('a')[1].string
        data = []
        for feature in features:
            if feature in title:
                data.append(title)
                #解析时间
                html = str(txList)
                childURL = 'https://www.woyaogexing.com'+''.join(re.findall(findHref, html))
                child_html = askURL(childURL)
                childSoup = BeautifulSoup(child_html, 'html.parser')
                time = re.findall(findTime, str(childSoup))
                data.append(time)
                #解析图片链接
                lazy = childSoup.find_all('img', class_='lazy')
                imgURL = re.findall(r'src="(.*?)"', str(lazy))
                data.append(imgURL)
                # data = data + imgURL
                dataList.append(data)
                break
    return dataList

#excel保存
# def saveData(dataList, savePath):
#     wordbook = xlwt.Workbook(encoding='utf-8')
#     wordsheet = wordbook.add_sheet('豆瓣电影Top250', cell_overwrite_ok=True)
#     columns = ['title', 'time', 'imgURL']
#     for i in range(3):
#         wordsheet.write(0, i, columns[i])
#     for i in range(len(dataList)):
#         data = dataList[i]
#         for j in range(len(dataList[0])):
#             wordsheet.write(i+1, j, '\n'.join(data[j]))
#     wordbook.save(savePath)

def init_db(dbpath):
    sql = '''
        create table headImages(
            标题 varchar(20),
            日期 datatime,
            图片链接 text
        )

    '''
    conn = sqlite3.connect(dbpath)
    cursor = conn.cursor()
    cursor.execute(sql)
    conn.commit()
    conn.close()

#数据库保存
def saveData(dataList, dbpath):
    init_db(savaDB)
    conn = sqlite3.connect(dbpath)
    cur = conn.cursor()

    for data in dataList:
        data[0] = "'" + data[0] + "'"
        data[1] = "'" + '\n'.join(data[1]) + "'"
        data[2] = "'" + '\n'.join(data[2]) + "'"
        # data[index] = list(data[index])
        sql = """
                insert into headImages(标题,日期,图片链接)
                values(%s)
        """%','.join(data)
        # print(sql)
        cur.execute(sql)

    conn.commit()
    cur.close()
    conn.close()



if __name__ == '__main__':
    savePath = r'./头像.xls'
    savaDB = r'./头像.db'
    html = askURL(url)
    dataList = getData(html)
    # print(dataList)
    # saveData(dataList, savePath)
    saveData(dataList, savaDB)