爬虫原理
简单来讲就是以代码逻辑模拟浏览器(应用)访问,自动化地来获取目标数据,一般就是基于HTTP、HTTPS等网络协议,基于URL进行网络请求访问;然后解析响应内容。
robots.txt
协议
进行网站内容爬取之前,需要考虑法律问题,一般约定俗成的就是**robots.txt
协议,** 这个协议,也被称为爬虫协议或机器人协议,它的核心目的是告诉网络爬虫哪些页面可以抓取,哪些页面不可以抓取。通过一系列的规则 来指导搜索引擎爬虫的行为,如允许或禁止爬虫访问特定的页面或目录。robots.txt
文件通常位于网站根目录下,正规网站可以直接在地址栏进行访问,例如:
-
User-Agent: *
这行指定了规则适用于所有类型的爬虫(搜索引擎的机器人)。星号(*)是一个通配符,代表"所有"。 -
Allow: /
这行表示允许所有爬虫访问网站的根目录。这是一个比较宽松的规则,因为通常网站的内容都是从根目录开始的。 -
Disallow: /?*
这行表示禁止爬虫访问任何以问号(?)开始的URL。这通常用于动态页面或查询字符串,意味着爬虫不能抓取任何查询参数。 -
Disallow: /*/tag/*/?*
这行表示禁止爬虫访问任何包含/tag/
的URL,并且该URL后有问号(?)的查询字符串。 -
Disallow: /*/tag/*/default.html?*
这行表示禁止爬虫访问任何以/tag/
开始并以/default.html?
结尾的URL,同样适用于带有查询字符串的情况。 -
Disallow: /index.html*
这行表示禁止爬虫访问任何以/index.html
开头的URL。这可能用于防止爬虫抓取特定页面的不同版本或变体。 -
Disallow: /default.aspx*
这行表示禁止爬虫访问任何以/default.aspx
开头的URL。这通常用于ASP.NET网站,用于防止爬虫抓取特定页面的不同版本或变体。 -
Sitemap: https://www.cnblogs.com/sitemap.xml
这行指定了网站地图(sitemap)的URL。网站地图是一个XML文件,其中列出了网站中所有可供爬虫抓取的页面的URL。通过指定这个URL,网站管理员告诉爬虫哪些页面是他们希望被索引的。
robots常见规则:
|-------------|-----------------------------------------------------------------------------|----------------------------------------|
| 规则标识 | 含义 | 样例 |
| User-agent | 这个指令用于指定规则适用于哪些网络爬虫。它可以是一个具体的爬虫名称,如Googlebot
,或者使用通配符*
来表示所有爬虫。 | * |
| Disallow | 这个指令用于指定不允许爬虫访问的URL路径。它可以是完整的URL或者路径模式。如果User-agent
后面跟着*
,则表示该规则适用于所有爬虫。 | /admin/
表示禁止爬虫访问/admin/
目录下的所有页面。 |
| Allow | 这个指令用于指定允许爬虫访问的URL路径。与Disallow
相反,Allow
指令通常在Disallow
规则之后指定特定的例外。 | /public/
表示允许爬虫访问/public/
目录下的所有页面。 |
| Crawl-delay | 这个指令用于指定爬虫在两次抓取之间的延迟时间(以秒为单位)。这个指令是可选的,用于控制爬取速度,防止对服务器造成过大压力。 | 5
表示爬虫在访问下一个页面前应该至少等待5秒。 |
Python爬虫基本类库
爬虫的第一步就是获取目标的数据,这里要用到的就是请求库
请求库
python一般是使用requests 库、aiohttp 库等,本篇文章主要是以request为基础,requests的API文档:开发接口 --- Requests 2.18.1 文档
一般使用request的get、post等请求方法,通过构造cookie、header等http协议相关的参数进行接口、页面请求,返回内容为 Reponse Object,内容一般以 reponse.text进行获取。 例如:
python
import requests
#百度 robot url
robot_url = "https://www.baido.com/robots.txt"
#模拟浏览器headers
header = {
"accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"accept-encoding":"gzip, deflate, br, zstd",
"accept-language":"zh-CN,zh;q=0.9",
"cache-control":"max-age=0"
}
#发起请求
reponse = requests.get(robot_url,headers=header)
#输出内容
print(reponse.text)
解析库
通过接口、地址调用后,需要将返回的数据进行解析,提炼出需要的数据。这时候就要用到解析库
json
json为python本身自带的类库,主要用于针对固定API或者返回内容为json结构的数据进行解析:
主要方法包括:
|--------------|------------------------------|
| 方法 | 作用 |
| json.dumps() | 将Python对象编码成json字符串 |
| json.loads() | 将json字符串解码成Python对象 |
| json.dump() | 将Python对象序列化为json对象后写入文件 |
| json.load() | 读取文件中json形式的字符串元素转化为Python类型 |
dumps和loads使用样例:
python
import json
print("-----------------dumps()用法样例------------")
demo = {
'name':'张三',
'rel':[
{
'id':1,
'patten':'sdfwsdfssdf'
},
{
'id':2,
'patten':'sdfwsdfssdf'
}
],
'cont':(7,1,2,2,2,1)
}
#ensure_ascii=false是不进行转码
json_domo = json.dumps(demo,ensure_ascii=False)
print("将python对象转为json{0}".format(json_domo))
print("-----------------loads()用法样例------------")
demo_str_one = "{ \"name\":\"王五\"}"
print("将json字符串转为就python对象:")
print(json.loads(demo_str_one))
输出:
bash
-----------------dumps()用法样例------------
将python对象转为json{"name": "张三", "rel": [{"id": 1, "patten": "sdfwsdfssdf"}, {"id": 2, "patten": "sdfwsdfssdf"}], "cont": [7, 1, 2, 2, 2, 1]}
-----------------loads()用法样例------------
将json字符串转为就python对象:
{'name': '王五'}
dump和load使用样例
python
import json
print("-----------------dump()用法样例------------")
demo_obj = {
'age':12,
'sex':'男'
}
print("将python对象序列化为json并写入文件")
with open('demo_obj.txt', 'w', encoding='utf-8') as f:
#ensure_ascii=False 以保留非 ASCII 字符,indent=4 用于美化输出,sort_keys=True 用于确保字典的键按照字母顺序排列。
json.dump(demo_obj, f, ensure_ascii=False, indent=4, sort_keys=True)
#demo_obj.txt文件内容样例:
# {
# "age": 12,
# "sex": "男"
# }
print("-----------------load()用法样例------------")
#jsonfile.txt文件内容样例:
# {
# "name": "胡汉三",
# "sex": "男",
# "student":[
# {
# "id":1
# }
# ]
# }
with open('jsonfile.txt', 'r', encoding='utf-8') as file:
# 使用 json.load 函数从文件中加载 JSON 数据
data = json.load(file)
print("jsonfile.txt 中内容为:")
print(data)
BeautifulSoup库
BeautifulSoup
是一个用于解析HTML和XML文档的Python库。它创建了一个解析树,可以方便地提取HTML和XML中的数据。BeautifulSoup
支持多种解析器,包括Python内置的 html.parser
和 xml.parser
,以及第三方的 lxml
和 html5lib
。
不同于json,使用BeautifulSoup需要先进行安装:pip install beautifulsoup4
关于beautifulSoup库的具体使用可以查看官方网站:Beautiful Soup 中文文档
以下为样例:
python
from bs4 import BeautifulSoup
# 假设我们有一段HTML代码
html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
</body>
</html>
"""
# 使用BeautifulSoup解析HTML
soup = BeautifulSoup(html_doc, 'html.parser')
# 获取所有<a>标签
a_tags = soup.find_all('a')
# 打印每个<a>标签的文本和href属性
for tag in a_tags:
print(tag.get_text(), tag['href'])
# 使用CSS选择器获取第一个<a>标签
first_a_tag = soup.select_one('a.sister')
print(first_a_tag.get_text())
# 修改第一个<p>标签的内容
first_p = soup.find('p', class_='title')
first_p.string = "New Title"
# 打印修改后的HTML
print(soup.prettify())
存储库
将爬取的数据解析后,就需要进行存储,便于后续的分析、使用,一般常用的有两种,一种是放入数据库,另一种生成相关的excel文件
数据库
一般使用SQLLite或者Mysql等其他相关数据库,这里以SQLLite为例进行解释。
QLite是一个轻量级的数据库管理系统,它被设计为不需要一个独立的服务器进程或系统。在Python中,sqlite3
模块已经内置在标准库中,所以你不需要单独安装SQLite来在Python程序中使用它。这意味着只要你有Python环境,你就可以使用SQLite数据库。
首先封装一个SQLLite的工具类:
python
import sqlite3
class SQLLite:
__db_conn = None
def __init__(self,dbname=''):
try:
self.__db_conn = sqlite3.connect(dbname)
except:
raise Exception("SQLLite not exists")
def __del__(self):
self.__db_conn.close()
def save_dict_objects(self,table_name = '',objects=[]):
if table_name != '' and len(objects) > 0 and isinstance(objects[0],dict):
table = self.excute_query_sql("select COUNT(*) AS isexsits from sqlite_master where type = 'table' and name = '"+table_name+"'")
if table[0]['isexsits'] > 0:
self.__save_data_not_sql(table_name=table_name,datas=objects)
else:
default_sql = "CREATE TABLE IF NOT EXISTS "+table_name+" "
temp = objects[0]
default_sql = default_sql + self.__get_cloum_from_dict(temp)
self.excute_not_query_sql(sqls=default_sql)
self.__save_data_not_sql(table_name=table_name,datas=objects)
return len(objects)
else:
raise Exception("table_name must not empty!")
def excute_not_query_sql(self,sqls):
cursor = self.__db_conn.cursor()
if sqls != '':
try:
cursor.execute(sqls)
self.__db_conn.commit()
except:
cursor.close()
raise Exception("SQL is ERROR")
return ""
def excute_query_sql(self,sql):
cursor = self.__db_conn.cursor()
if sql != '':
try:
cursor.execute(sql)
ret = cursor.fetchall()
self.__db_conn.commit()
field_dic = dict()
index = 0
for field in cursor.description:
field_dic[field[0]] = index
index = index + 1
result = []
for data in ret:
resi = dict()
for fieldi in field_dic:
resi[fieldi] = data[field_dic[fieldi]]
result.append(resi)
return result
except:
cursor.close()
raise Exception("SQL is ERROR")
return []
def __save_data_not_sql(self,table_name,datas):
cursor = self.__db_conn.cursor()
try:
for data in datas:
vs = tuple(data.values())
sql = "INSERT INTO " + table_name + " VALUES " + str(vs) + ";"
cursor.execute(sql)
self.__db_conn.commit()
cursor.close()
except:
raise Exception("unknown error")
return len(datas)
def __get_cloum_from_dict(self,dict_data):
ret_str = "("
keys = dict_data.keys()
for field in keys:
ret_str = ret_str + str(field)
if isinstance(dict_data[field],(int)):
ret_str = ret_str + " INTEGER,"
elif isinstance(dict_data[field],(float)) :
ret_str = ret_str + + " REAL,"
else:
ret_str = ret_str + " TEXT,"
sql = ret_str[:-1] + ')'
return sql
进行调用测试:
python
from sqlliteUtil import SQLLite
sql = SQLLite(dbname="db_test")
sql.excute_not_query_sql("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT);")
sql.excute_not_query_sql("INSERT INTO users (name) VALUES ('Alice');")
sql.excute_not_query_sql("INSERT INTO users (name) VALUES ('张三');")
ret = sql.excute_query_sql("SELECT * FROM users")
print(ret)
-------------------------------------------------------------------------------------------
输出:
[{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': '张三'}]
excel
一般是以excel文件的形式进行数据的存储,在Python中操作Excel文件,可以使用几个流行的库,如openpyxl
、xlrd
、xlwt
、pandas
等,相关使用可以查阅官方文档。
简单样例
准备工作完成,进行一个简单基础Demo的编写,这里以博客园首页为例,爬取其每天推送博客的前10篇文章;
先进行观察其首页特点,F12查看源码
通过分析可以看到,我们的目标主要集中在a标签中,这里面记录了 标题和连接地址
首先定义一个博客数据的目标结构
javascript
{
"art_name":"", // 文章名称
"art_desc":"", // 文章描述
"art_url":"", //文章url
"art_time":"" //文章发布时间
}
接下来就是构建爬虫:获取数据,组装成目标数据结构,进行存储了。
python
import requests
from bs4 import BeautifulSoup
from sqlliteUtil import SQLLite
robot_url = "https://www.cnblogs.com"
#模拟浏览器headers
header = {
"accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"accept-encoding":"gzip, deflate, br, zstd",
"accept-language":"zh-CN,zh;q=0.9",
"cache-control":"max-age=0"
}
#请求其页面
reponse = requests.get(robot_url,headers=header)
#使用BeautifulSoup 配合 html.parser进行内容解析
soup = BeautifulSoup(reponse.text, 'html.parser')
#获取返回内容中所有的文章信息的标签
div_tag = soup.findAll('div',class_='post-item-text')
feeter_tag = soup.findAll('footer',class_='post-item-foot')
#解析组装目标结构
ret = []
for i in range(0,len(div_tag)):
blog_data = dict()
title_tag = div_tag[i].find('a',class_='post-item-title')
desc_tag = div_tag[i].find('p',class_='post-item-summary')
time = feeter_tag[i].find('span',class_='post-meta-item').text
blog_data['art_name'] = str(title_tag.text)
blog_data['art_desc'] = str(desc_tag.text).replace("\n","")
blog_data['art_url'] = str(title_tag['href'])
blog_data['art_time'] = str(time).replace("\n","")
ret.append(blog_data)
#存储数据库中 db_blog
sql = SQLLite(dbname="db_blog")
sql.save_dict_objects(table_name='tb_artitle_data',objects=ret)
效果: