# 简介

开发文档:https://feapder.com/#/README

  1. feapder 是一款上手简单,功能强大的 Python 爬虫框架,内置 AirSpider、Spider、TaskSpider、BatchSpider 四种爬虫解决不同场景的需求。
  2. 支持断点续爬、监控报警、浏览器渲染、海量数据去重等功能。
  3. 更有功能强大的爬虫管理系统 feaplat 为其提供方便的部署及调度

# 轻量爬虫(AirSpider)demo

# 创建项目

选择 AirSpider,回车

feapder create -s air_spider_test
> AirSpider
  Spider
  TaskSpider
  BatchSpider

会生成一个爬虫文件 air_spider_test.py

# 编辑爬虫文件
# -*- coding: utf-8 -*-
"""
Created on 2024-03-21 10:06:00
---------
@summary:
---------
@author: yangxs
"""
import feapder
class Douban(feapder.AirSpider):
    def start_requests(self):
        for i in range(1, 6):
            yield feapder.Request(f"https://movie.douban.com/top250?start={i * 25}&filter=")
    def parse(self, request, response):
        lis = response.xpath('//ol[@class="grid_view"]//li')
        for li in lis:
            item = dict()
            item['title'] = li.xpath('.//span[@class="title"]/text()').extract_first()
            item['score'] = li.xpath('.//span[@class="rating_num"]/text()').extract_first()
            item['blurb'] = li.xpath('.//span[@class="inq"]/text()').extract_first()
            item['href_info'] = li.xpath('.//div[@class="pic"]/a/@href').extract_first()
            yield feapder.Request(
                url=item['href_info'],
                callback=self.detail_parse,
                item=item
            )
    def detail_parse(self, request, response):
        item = request.item
        item['details'] = response.xpath('//span[@property="v:summary"]/text()').extract_first().strip()
        yield item
if __name__ == "__main__":
    Douban().start()
  • start_requests() 发送请求的 url
  • parse() 解析函数

# 用 feapder 连接 MySQL 数据库

from feapder.db.mysqldb import MysqlDB
db = MysqlDB(
    ip="192.168.10.129", port=3306, db="spider", user_name="root", ser_pass="123456"6
)
# 创建表
sql_creat_table = """
    create table if not exists douban_feapder(
        id int primary key auto_increment,
        title varchar(100) not null,
        score varchar(100),
        blurb varchar(100),
        href_info varchar(100),
        details text
    );
"""
db.execute(sql_creat_table)
# 添加数据
# ignore: 当插入数据的 id 已存在时,则直接忽略
sql_save = "insert ignore into douban_feapder(title,score,blurb,href_info,details) values (%s,%s,%s,%s,%s)"
db.add(sql_save % ('1', '2', '3', '4', '5'))
# 通过 setting.py 配置文件连接 MySQL 数据库
# 创建配置文件
feapder create --setting

会生成 setting.py 文件,在此文件中配置 MySQL 链接相关信息

# MYSQL
MYSQL_IP = "192.168.10.129"
MYSQL_PORT = 3306
MYSQL_DB = "spider"
MYSQL_USER_NAME = "root"
MYSQL_USER_PASS = "123456"
# 生成 item 文件

在执行此命令前,需要先创建后对应的 MySQL 数据表

feapder create -i 表名
  Item
 >Item 支持字典赋值
  UpdateItem
  UpdateItem 支持字典赋值

再将生成的 item 文件中的类导入爬虫脚本代码文件中

from douban_feapder_item import DoubanFeapderItem
item = DoubanFeapderItem()

然后将爬虫脚本提取的字段赋值给 item 对象,最后 yield item ,启动爬虫后,数据就会自动存入对应的数据库表

# 整体流程
  1. 创建 spider

    feapder create -s douban
    > AirSpider
      Spider
      TaskSpider
      BatchSpider
     
    # 会自动生成 douban.py,这个文件就是主要的爬虫启动文件
  2. 创建配置值文件

    feapder create --setting
    # 会自动生成一个 setting.py 文件

    并设置 setting.py 中的 MySQL 数据库连接参数

    # MYSQL
    MYSQL_IP = "192.168.10.129"
    MYSQL_PORT = 3306
    MYSQL_DB = "spider"
    MYSQL_USER_NAME = "root"
    MYSQL_USER_PASS = "123456"
  3. 创建数据库表

    自己创建一个 py 文件,用于执行创建数据库表的 SQL 语句

    from feapder.db.mysqldb import MysqlDB
    db = MysqlDB()
    sql_creat_table = """
        create table if not exists douban_feapder(
            id int primary key auto_increment,
            title varchar(100) not null,
            score varchar(100),
            blurb varchar(100),
            href_info varchar(100),
            details text
        );
    """
    db.execute(sql_creat_table)

    直接运行即可,执行完后到自己的 MySQL 数据库查看是否 douban_feapder 表是否创建成功,若没创建成功,可手动创建

  4. 创建 item 文件

    feapder create -i douban_feapder
      Item
     >Item 支持字典赋值
      UpdateItem
      UpdateItem 支持字典赋值
      
    # 会自动生成 douban_feapder_item.py
    • douban_feapder --> 上一步中创建的表名
  5. 编辑第一步创建的 spider 文件

    # douban.py
    # -*- coding: utf-8 -*-
    """
    Created on 2024-03-21 10:06:00
    ---------
    @summary:
    ---------
    @author: yangxs
    """
    import feapder
    from douban_feapder_item import DoubanFeapderItem
    class Douban(feapder.AirSpider):
        def start_requests(self):
            for i in range(1, 6):
                yield feapder.Request(f"https://movie.douban.com/top250?start={i * 25}&filter=")
        def parse(self, request, response):
            lis = response.xpath('//ol[@class="grid_view"]//li')
            for li in lis:
                item = DoubanFeapderItem()
                item['title'] = li.xpath('.//span[@class="title"]/text()').extract_first()
                item['score'] = li.xpath('.//span[@class="rating_num"]/text()').extract_first()
                item['blurb'] = li.xpath('.//span[@class="inq"]/text()').extract_first()
                item['href_info'] = li.xpath('.//div[@class="pic"]/a/@href').extract_first()
                yield feapder.Request(
                    url=item['href_info'],
                    callback=self.detail_parse,
                    item=item
                )
        def detail_parse(self, request, response):
            item = request.item
            item['details'] = response.xpath('//span[@property="v:summary"]/text()').extract_first().strip()
            print(item)
            yield item
    if __name__ == "__main__":
        # thread_count 指定 5 个线程运行
        Douban(thread_count=5).start()
    • 爬虫脚本在解析提取数据字段时,存入 item 对象中数据对应的 key,要与创建的 douban_feapder_item.py 中__init__下的字段名保持一致
  6. 最后直接运行爬虫脚本文件 douban.py 即可

# 多数据库存储

将爬下来的数据同时存储到 MySQL 和 MongoDB

# 创建爬虫项目
feapder create -p ttjijin
# 创建 Item

在创建 item 之前,先创建好 MySQL 数据库表

创建好数据库表后,执行 item 创建命令 (在 /item 路径下执行命令)

feapder create -i ttjijin
  Item
 >Item 支持字典赋值
  UpdateItem
  UpdateItem 支持字典赋值

如果创建 item 时 -i 后面接的名称就是数据库表的名称,就不需要修改 __table_name__ ,否则需要修改成对应的表名

# ttjijin_item.py
from feapder import Item
class TtjijinItem(Item):
    """
    This class was generated by feapder
    command: feapder create -i ttjijin 
    """
    __table_name__ = "ttjijin"
    def __init__(self, *args, **kwargs):
        self.daily_growth_rate = kwargs.get('daily_growth_rate')  # 日增长率
        self.dtime = kwargs.get('dtime')  # 日期
        ......
# 配置 setting.py

配置 MySQL 与 MongoDB 的链接参数,并打开 Pipelines 管道

# MYSQL
MYSQL_IP = "192.168.10.129"
MYSQL_PORT = 3306
MYSQL_DB = "spider"
MYSQL_USER_NAME = "root"
MYSQL_USER_PASS = "123456"
# MONGODB
MONGO_IP = "192.168.10.129"
MONGO_PORT = 27017
MONGO_DB = "spider"
MONGO_USER_NAME = "root"
MONGO_USER_PASS = "123456"
ITEM_PIPELINES = [
    "feapder.pipelines.mysql_pipeline.MysqlPipeline",
    "feapder.pipelines.mongo_pipeline.MongoPipeline",
]
# 创建 pipeline.py

在 main.py 同级路径创建 pipelines 文件夹,并创建 mong_pipeline.py、mysql_pipeline.py

image-20240320171758876

编辑内容

# mong_pipeline.py
from feapder.db.mongodb import MongoDB
from feapder.pipelines import BasePipeline
class MongoPipeline(BasePipeline):
    def __init__(self):
        self.db = MongoDB()
    def save_items(self, table, items):
        self.db.add_batch(coll_name='ttjijin', datas=items)
# mysql_pipeline.py
from feapder.db.mysqldb import MysqlDB
from feapder.pipelines import BasePipeline
from items.ttjijin_item import TtjijinItem
class MysqlPipeline(BasePipeline):
    def __init__(self):
        self.db = MysqlDB()
    def save_items(self, table, items):
        sql = "insert ignore into ttjijin (no,cname,ename,dtime,iopv," \
              "ljjz,daily_growth_rate,week,month,three_month," \
              "six_month,year,two_year,three_year,this_year,since,procedure_fee) " \
              "values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)"
        datas = []
        for item in items:
            item = TtjijinItem(item)
            datas.append([item.no, item.cname, item.ename, item.dtime, item.iopv, item.ljjz,
                          item.daily_growth_rate, item.week, item.month, item.three_month,
                          item.six_month, item.year, item.two_year, item.three_month,
                          item.since, item.procedure_fee])
        self.db.add_batch(sql=sql, datas=datas)
# 创建编辑 spider 文件

执行爬虫脚本创建命令(/spiders 路径下执行)

feapder create -s ttjijin
> AirSpider
  Spider
  TaskSpider
  BatchSpider

编辑爬虫脚本文件 ttjijin.py

import random
import re
import feapder
from items.ttjijin_item import TtjijinItem
class Ttjijin(feapder.AirSpider):
    def start_requests(self):
        url = "https://fund.eastmoney.com/data/rankhandler.aspx"
        headers = {
            "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
            "Referer": "https://fund.eastmoney.com/data/fundranking.html",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
        }
        for i in range(1, 11):
            params = {
                "op": "ph",
                "dt": "kf",
                "ft": "all",
                "rs": "",
                "gs": "0",
                "sc": "qjzf",
                "st": "desc",
                "sd": "2023-03-20",
                "ed": "2024-03-20",
                "qdii": "",
                "tabSubtype": ",,,,,",
                "pi": str(i),
                "pn": "50",
                "dx": "1",
                "v": str(random.random())
            }
            yield feapder.Request(url, headers=headers, params=params, method="GET")
    def parse(self, request, response):
        # 提取网站 title
        res = response.text
        data = re.findall("datas:\[(.*?)\]", res)
        lis = data[0][1:-1].split('","')
        for li in lis:
            item = TtjijinItem()
            row = li.split(',')
            item.no = row[0]
            item.cname = row[1]
            item.ename = row[2]
            item.dtime = row[3]
            item.iopv = row[4]
            item.ljjz = row[5]
            item.daily_growth_rate = row[6] + '%' if row[6] else ''
            item.week = row[7] + '%' if row[7] else ''
            item.month = row[8] + '%' if row[8] else ''
            item.three_month = row[9] + '%' if row[9] else ''
            item.six_month = row[10] + '%' if row[10] else ''
            item.year = row[11] + '%' if row[11] else ''
            item.two_year = row[12] + '%' if row[12] else ''
            item.three_year = row[13] + '%' if row[13] else ''
            item.this_year = row[14] + '%' if row[14] else ''
            item.since = row[15] + '%' if row[15] else ''
            item.procedure_fee = row[20]
            yield item
if __name__ == "__main__":
    Ttjijin(thread_count=5).start()

最后运行爬虫脚本 ttjijin.py 即可

# 中间件

# 默认中间件

设置 userAgent,从写 download_midware 方法(位置:爬虫脚本文件)

import feapder
class Baidu(feapder.AirSpider):
    def download_midware(self, request):
        request.headers = {'User-Agent': '1111111111'}
    def start_requests(self):
        yield feapder.Request("https://www.baidu.com")
    def parse(self, request, response):
        # 提取网站 title
        print(response.xpath("//title/text()").extract_first())
        # 提取网站描述
        print(response.xpath("//meta[@name='description']/@content").extract_first())
        print("网站地址: ", response.url)
if __name__ == "__main__":
    Baidu().start()
  • download_midware() 就是默认中间件的函数名,若在 start_requests() 函数 yield feapder.Request() 时不指定中间件,则会使用默认的中间件函数,即 download_midware()
# 自定义中间件
import feapder
class Baidu(feapder.AirSpider):
    def my_download_midware(self, request, response):
        request.headers = {'User-Agent': '1111111111'}
    def start_requests(self):
        yield feapder.Request("https://www.baidu.com", download_midware=my_download_midware)
    def parse(self, request, response):
        # 提取网站 title
        print(response.xpath("//title/text()").extract_first())
        # 提取网站描述
        print(response.xpath("//meta[@name='description']/@content").extract_first())
        print("网站地址: ", response.url)
if __name__ == "__main__":
    Baidu().start()
  • 自定义中间件,只需要在 start_requests() 函数 yield feapder.Request() 时指定 download_midware=自定义中间件函数名 即可
  • 注意:若自定义中间要处理请求,则返回 request;若中间件要处理响应,则返回 response
  • 若同时要指定多个中间件, yield feapder.Request() 时指定 download_midware=[中间件1,中间件2,...] 即可