网络爬虫

Eric讨论 | 贡献2022年1月15日 (六) 16:10的版本

网络爬虫(web crawler),也叫网络蜘蛛(spider),是一种用来自动抓取Web信息数据的程序或者脚本。搜索引擎和其他一些网站使用网路爬虫更新他们的内容。

简介

获取页面

以下是基于Python 3 编程语言的爬虫。

request

request模块是Python的urllib标准库下的模块,直接使用即可。request模块可以方便获取URL内容。

from urllib.request import urlopen 
html = urlopen('https://www.baidu.com') 
print(html.read())

了解更多 >> Python文档:标准库urllib - request 用于打开 URL 的可扩展库


requests

requests是一个Python第三方HTTP库。该软件的目的是使HTTP请求更简单,更人性化。

可以使用pip安装requests:

pip install requests

使用 Requests 发送网络请求非常简单。如获取某个网页,使用get函数,会返回一个Response对象,我们可以从这个对象中获取所有我们想要的信息。

import requests
response = requests.get('https://www.baidu.com')
print(response.content)

了解更多 >> Requests文档 Requests源代码


解析提取

BeautifulSoup

Beautiful Soup是一个Python第三方库,可以用来解析html文档,方便提取需要的数据。

可以使用pip安装:

pip install beautifulsoup4

如使用BeautifulSoup提取一个页面标题

from urllib.request import urlopen
from bs4 import BeautifulSoup

html = urlopen('https://www.baidu.com')

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

XPath

XPath即XML路径语言(XML Path Language),一种查询语言,用于在XML中选择节点。在Python中,可以使用lxml这个第三方库来解析页面,然后通过XPath来选取内容。

可以使用pip安装lxml:

pip install lxml

如下简单示例,使用requests获取页面,使用lxml解析并用XPath提取标题文本。

import requests 
from lxml import etree 

response = requests.get('https://www.baidu.com')
html = etree.HTML(response.content) 
title = html.xpath('//title//text()')

表格提取

Pandas

如果只要获取页面中的表格,可以使用Pandasread_html()函数,简单方便。可以使用pip安装Pandas:

pip install pandas

获取某个网页上所有表格,生成一个由DataFrame对象组成的列表:

import pandas as pd
dfs = pd.read_html('https://pandas.pydata.org/docs/reference/io.html')

read_html()函数默认使用lxml解析,还可以设置为BeautifulSoup4和html5lib。函数格式pandas.read_html(*args, **kwargs)。可选参数

了解更多 >> Pandas API:read_html Pandas 指南:IO工具 - HTML


BeautifulSoup + Pandas

XPath + Pandas

URL解析

名称 描述 示例
urllib.parse 用于解析 URL,Python标准库,使用时导入即可。
from urllib import parse

常用函数:
urlparse() 将URL字符串分割成其组成部分。
quote() URL编码。
unquote() URL解码。
parse.unquote('https://example.com/%E9%A6%96%E9%A1%B5')
输出'https://example.com/首页'

数据存储

二进制文件

简单方便,数据不经过解码编码处理,直接将二进制格式保存为文件。一般用于图片,音频,视频,或响应的原文件等。下面使用Python语言保存图片:

import requests
import os

res = requests.get('http://img0.bdstatic.com/static/searchresult/img/logo-2X_32a8193.png')

with open('download.png', 'wb') as f:    # 'wb'表示以二进制格式打开文件用于写入
    f.write(res.content)           # res.content为响应的二进制格式内容
    print("图片保存完成。")

实际存储中,可以进一步完善,如保存过的文件不再下载,文件名与网页中相同。示例如下:

import os
import requests

def download_file(url, save_path,headers=None):
    save_path = os.path.expanduser(save_path)
    file_name = url.split('/')[-1]
    file_path = os.path.join(save_path, file_name)  #直接加号拼接需注意:windows使用\和liunxs使用/分割路径。

    if not os.path.exists(save_path):   #判断是否已存在下载目录
        os.makedirs(save_path)  #新建下载目录

    if not os.path.exists(file_path):     #判断是否已存在
        res = requests.get(url,headers=headers)
        with open(file_path, 'wb') as f:    # 'wb'表示以二进制格式打开文件用于写入
            f.write(res.content)           # res.content为响应的二进制格式内容
            print("{}文件保存完成。".format(file_path))
    else:
        print("文件已存在。")
    
download_file('http://img0.bdstatic.com/static/searchresult/img/logo-2X_32a8193.png', '~/图片保存目录')

Python读写文件主要使用open()函数,该函数返回一个文件对象(file object),如果执行失败会抛出OSError。文件对象共有3种类别:原始二进制文件, 缓冲二进制文件,文本文件。

函数open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)最常用的是file和modec参数。如`file_test = open('test.txt', 'r')`表示以只读文本模式创建文件对象,存储在file_test变量中。参数mode是一个可选字符串,用于指定打开文件的模式,默认值是 'r' 。文件对象格式默认为文本模式('t'),可以指定二进制模式('b'),可用的模式有:

模式 描述 文件不存在 文件的指针位置 写操作下原文件内容
r 只读 抛出FileNotFoundError 文件的开头 不能写
rb 二进制模式只读 抛出FileNotFoundError 文件的开头 不能写
r+ 读写 抛出FileNotFoundError 文件的开头 清空内容
rb+ 二进制模式读写 抛出FileNotFoundError 文件的开头 清空内容
w 只写 创建文件 文件的开头 清空内容
wb 二进制模式只写 创建文件 文件的开头 清空内容
w+ 读写 创建文件 文件的开头 清空内容
wb+ 二进制模式读写 创建文件 文件的开头 清空内容
a 只写追加 创建文件 文件结尾 不变
ab 二进制模式只写追加 创建文件 文件结尾 不变
a+ 读写追加 创建文件 文件结尾 不变
ab+ 二进制模式读写追加 创建文件 文件结尾 不变

注:

  • r+, rb+ 模式下,如果文件对象先调用read()再调用write(),等同与追加模式
  • FileNotFoundError为OSError的子类

在处理文件对象时,最好使用 with 关键字。能够在结束时自动关闭文件对象,不需要自己调用文件对象的close()函数。

了解更多 >> Python文档:读写文件 Python文档:内置函数-open() Python文档:术语对照表-文件对象


纯文本文件

纯文本文件也简单方便,不用转为其他数据格式,直接存储即可。如:

# 保存文本
with open('test.txt', 'w') as f: 
    data = 'txt data'
    f.write(data)
    print("保存完成")
    
# 读取文本
with open('test.txt', 'r') as f: 
    data = f.read()
    print(data)

CSV文件

CSV (Comma Separated Vaules) 格式是电子表格和数据库中最常见的输入、输出文件格式。csv格式一般为:一行一条记录,记录间不同字段值用逗号分隔。如

code, open, high, low, close,
apple, 12, 16, 11.2, 14, 
pear, 12.5, 15, 12.5, 13.5
   

在python中处理csv格式可以使用标准库csv,直接导入即可。方便用户进行列表或字典格式与csv格式转化。

csv文件的读取,使用csv.reader()或csv.DictReader()函数,函数会生成一个Reader对象。csv.reader()读取的每行以列表形式保存在可迭代对象Reader中;csv.DictReader()生成的Reader对象,每行为字典格式(python 3.8开始)。

csv文件的写入,使用csv.writer()或csv.DictWriter()函数,函数会生成一个Writer对象。Writer对象有2个写入函数:writerow()函数,用于写入单行;writerows(rows)函数,用于写入多行。

import csv

# 列表数据,写入csv文件
head_row = ['code', 'open', 'high', 'low', 'close',]
rows = [['apple',12, 16, 11.2, 14],
        ['pear', 12.5, 15, 12.5, 13.5],
       ]    
with open('test.csv','w', newline='') as f:
    csv_writer = csv.writer(f)     #生成一个Writer对象
    csv_writer.writerow(head_row)  #writerow写入单行  
    csv_writer.writerows(rows)     #writerows写入多行
    print('test.csv 保存完成 \n')

#  字典数据,写入csv文件
fieldnames = ['first_name', 'last_name']
row = {'first_name': 'Baked', 'last_name': 'Beans'}
rows = [{'first_name': 'Lovely', 'last_name': 'Spam'},
       {'first_name': 'Wonderful', 'last_name': 'Spam'},
       ]
with open('test.csv', 'w', newline='') as csvfile:
    writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
    writer.writeheader()
    writer.writerow(row)
    writer.writerows(rows)
    print('test.csv 保存完成 \n')

# 直接读取文本
with open('test.csv') as f:    
    data = f.read()
    print('test.csv直接读取文本内容如下:')
    print(data)

# 读取csv文件
with open('test.csv','r', ) as f:    
    rows = csv.reader(f)
    print('读取csv每行到列表:')
    for row in rows:
        print(row)

CSV文件的存储读取,也可以使用其他软件,如Pandas

了解更多 >> Python文档:标准库csv


JSON文件

JSON (JavaScript Object Notation) 是一种流行的数据交换格式。 JSON数据类型有:字符串(string)、数值(number)、true、false、 null、无序的键值对集合(object)或者有序的列表(array)。因为都是常见的数据结构,使得JSON格式数据可以在编程语言之间进行交换数据。object和array的值可以嵌套,这两种数据类型描述如下: * 无序的键值对集合(object):使用{ }表示,键与值之间用:分隔,键值对之间使用,分割。如{‘name’:‘a’, ‘age’:12} * 有序的列表(array):使用[ ]表示,值之间用,分隔。如[‘a’, 12]

下面一个json格式数据:

{
	"Image": {
		"Width": 800,
		"Height": 600,
		"Title": "View from 15th Floor",
		"Thumbnail": {
			"Url": "http://www.example.com/image/481989943",
			"Height": 125,
			"Width": 100
		},
		"Animated": false,
		"IDs": [116, 943, 234, 38793]
	}
}

Python中处理JSON格式可以使用标准库json。json编码器和解码器默认如下转化:

JSON数据类型 描述 转为python数据类型 由python数据类型转入
string 字符串,用双引号括起来 str str
number 数值 int或float int, float, int 和 float 派生的枚举
true 布尔值 true True True
false 布尔值 false False False
null 空值 None None
object 无序的键值对集合 dict dict
array 有序列表 list list, tuple

json常用两个函数:dumps()将python数据类型编码为json格式字符串,loads()将json格式字符串解码为python数据类型。对于从文件读写json格式数据,可以使用dump()和load()函数,会自动转换格式。

import json

data = {"name":"image", 'Height':800,'Animated':False}

# 格式转换
json_data = json.dumps(data)  #转为json格式字符串
print(type(json_data), json_data)

data = json.loads(json_data)  #转为python数据类型
print(type(data),data)

# 读取保存数据,并自动转换相应格式
with open('test.txt', 'r+') as f:
    json.dump(data, f)   #转化为json格式文本对象,并写入文件
    print("保存完成")

with open('test.txt', 'r+') as f:
    data = json.load(f)  #从文件读取json格式,并转化为python数据类型
    print(type(data),data)

了解更多 >> Python文档:输入输出-使用 json 保存结构化数据 Python文档:标准库json JSON官网


关系型数据库

常用关系型数据库SQLiteMysqlPostgreSQL等。

SQLite使用简单,不需要安装配置,一个数据库就是一个文件。Python中可以使用标准库sqlite3来操作。先创建一个 Connection 对象,它代表数据库。再调用Connection对象的cursor()方法创建一个 Cursor 游标对象。然后调用 Cursor对象的 execute() 方法来执行 SQL 语句。也可以使用Pandas软件来操作。

SQLite一共5种数据类型,如下:

SQLite类型 描述 从Python类型转入 默认转换为Python类型
NULL 空值 None None
INTEGER 整数,根据值的大小存储,最大8字节,即最大2^63-1=9223372036854775807。 int int
REAL 浮点数, 存储为 8 字节的 IEEE 浮点数字。 float float
TEXT 文本字符串,使用数据库编码(UTF-8、UTF-16BE 或 UTF-16LE)存储。 str 取决于 text_factory , 默认为 str
BLOB 二进制值,直接存储,不经过转换,如存储图片等。 bytes bytes

下面使用Python的标准库sqlite3操作示例:

import sqlite3

# 生成Connection 对象,它代表数据库。没有会创建一个。
conn = sqlite3.connect('test.db')

# 生成Cursor对象
cur = conn.cursor()

# 查询是否存在product表
sql = "SELECT count(*) FROM sqlite_master WHERE type='table' AND name='product';"
result = cur.execute(sql)  #调用execute函数执行sql语句
has_table = result.fetchone()   #调用一次fetchone()函数,获取查询结果集的下一行
if has_table[0]:   # 查询结果的每一行为元组,
    print('已存在product表。')
else:
    # sql语句
    sql = '''CREATE TABLE product
                 (id          INTEGER  PRIMARY KEY AUTOINCREMENT ,  
                 product_id   INTEGER   NOT NULL,
                 product_name TEXT,
                 supplier_id  INTEGER   NOT NULL, 
                 price        REAL, 
                 qty          real, 
                 create_time  TimeStamp NOT NULL DEFAULT CURRENT_TIMESTAMP,  -- 当前当地时间,以text数据类型保存 
                 create_time2 TimeStamp NOT NULL DEFAULT (datetime('now','localtime')),  -- 以text数据类型保存
                 create_time3 TimeStamp NOT NULL DEFAULT (datetime('now','utc'))  -- 以text数据类型保存
                 
                 )''' 

    # 调用execute函数执行sql语句, 创建product表
    cur.execute(sql)
    print('product表创建成功。')    
    
# 插入单行所有字段数据
cur.execute("INSERT INTO product VALUES (5, 37932,'apple','2812', 6.5, '545', 15, 2020,2)")
# 插入单行部分字段数据
sql = '''INSERT INTO product (product_id,product_name,supplier_id,price,qty)
VALUES (37932,'apple','2812', 6.5, 120);'''
cur.execute(sql)
conn.commit()
conn.close()

# 查询product表所有内容
conn = sqlite3.connect('test.db')
cur = conn.cursor()
result = cur.execute("SELECT * FROM product")
print("produc表内容如下:")
for row in result.fetchall():
    print(row)
      
#删除表
cur.execute("DROP TABLE product")
conn.commit()
conn.close()
print("已删除product表")

了解更多 >> Python文档:标准库 sqlite3 python文档:标准库 sqlite3-SQLite 与 Python 类型 SQLite文档:SQLite 3 数据类型


爬虫进阶

法律与道德

资源

参考资料