Python爬虫基础

时间:2024-10-27 08:15:49

爬虫原理

简单来讲就是以代码逻辑模拟浏览器(应用)访问,自动化地来获取目标数据,一般就是基于HTTP、HTTPS等网络协议,基于URL进行网络请求访问;然后解析响应内容。

robots.txt协议

进行网站内容爬取之前,需要考虑法律问题,一般约定俗成的就是robots.txt协议,这个协议,也被称为爬虫协议或机器人协议,它的核心目的是告诉网络爬虫哪些页面可以抓取,哪些页面不可以抓取。通过一系列的规则来指导搜索引擎爬虫的行为,如允许或禁止爬虫访问特定的页面或目录。robots.txt文件通常位于网站根目录下,正规网站可以直接在地址栏进行访问,例如:

  1. User-Agent: * 这行指定了规则适用于所有类型的爬虫(搜索引擎的机器人)。星号(*)是一个通配符,代表“所有”。

  2. Allow: / 这行表示允许所有爬虫访问网站的根目录。这是一个比较宽松的规则,因为通常网站的内容都是从根目录开始的。

  3. Disallow: /?* 这行表示禁止爬虫访问任何以问号(?)开始的URL。这通常用于动态页面或查询字符串,意味着爬虫不能抓取任何查询参数。

  4. Disallow: /*/tag/*/?* 这行表示禁止爬虫访问任何包含/tag/的URL,并且该URL后有问号(?)的查询字符串。

  5. Disallow: /*/tag/*/default.html?* 这行表示禁止爬虫访问任何以/tag/开始并以/default.html?结尾的URL,同样适用于带有查询字符串的情况。

  6. Disallow: /index.html* 这行表示禁止爬虫访问任何以/index.html开头的URL。这可能用于防止爬虫抓取特定页面的不同版本或变体。

  7. Disallow: /default.aspx* 这行表示禁止爬虫访问任何以/default.aspx开头的URL。这通常用于ASP.NET网站,用于防止爬虫抓取特定页面的不同版本或变体。

  8. 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进行获取。 例如:

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类型

 dumpsloads使用样例:

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))

输出:

-----------------dumps()用法样例------------
将python对象转为json{"name": "张三", "rel": [{"id": 1, "patten": "sdfwsdfssdf"}, {"id": 2, "patten": "sdfwsdfssdf"}], "cont": [7, 1, 2, 2, 2, 1]}    
-----------------loads()用法样例------------
将json字符串转为就python对象:
{'name': '王五'}

 dumpload使用样例

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.parserxml.parser,以及第三方的 lxmlhtml5lib

不同于json,使用BeautifulSoup需要先进行安装:pip install beautifulsoup4

关于beautifulSoup库的具体使用可以查看官方网站:Beautiful Soup 中文文档

以下为样例:

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的工具类:

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

进行调用测试:

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文件,可以使用几个流行的库,如openpyxlxlrdxlwtpandas等,相关使用可以查阅官方文档。

简单样例

准备工作完成,进行一个简单基础Demo的编写,这里以博客园首页为例,爬取其每天推送博客的前10篇文章;

先进行观察其首页特点,F12查看源码

通过分析可以看到,我们的目标主要集中在a标签中,这里面记录了 标题和连接地址

首先定义一个博客数据的目标结构

{
 "art_name":"", // 文章名称
 "art_desc":"", // 文章描述
 "art_url":"",  //文章url
 "art_time":""  //文章发布时间
}

接下来就是构建爬虫:获取数据,组装成目标数据结构,进行存储了。

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)

效果: