Python 爬虫-feapder 框架简介

feapder 框架

学习目标

  • 掌握便捷式框架操作流程

  • 掌握请求钩子结构使用

  • 掌握框架项目搭建流程

  • 掌握数据入库与去重

1 简介

国内文档:https://boris-code.gitee.io/feapder

feapder 是一款上手简单,功能强大的Python爬虫框架,使用方式类似scrapy,方便由scrapy框架切换过来,框架内置3种爬虫:

1.1 支持的场景

  • AirSpider爬虫比较轻量,学习成本低。面对一些数据量较少,无需断点续爬,无需分布式采集的需求,可采用此爬虫。
  • Spider是一款基于redis的分布式爬虫,适用于海量数据采集,支持断点续爬、爬虫报警、数据自动入库等功能
  • BatchSpider是一款分布式批次爬虫,对于需要周期性采集的数据,优先考虑使用本爬虫。

1.2 架构说明

image

1.2.1模块说明
  • spider 框架调度核心
  • parser_control 模版控制器,负责调度parser
  • collector 任务收集器,负责从任务队里中批量取任务到内存,以减少爬虫对任务队列数据库的访问频率及并发量
  • parser 数据解析器
  • start_request 初始任务下发函数
  • item_buffer 数据缓冲队列,批量将数据存储到数据库中
  • request_buffer 请求任务缓冲队列,批量将请求任务存储到任务队列中
  • request 数据下载器,封装了requests,用于从互联网上下载数据
  • response 请求响应,封装了response, 支持xpath、css、re等解析方式,自动处理中文乱码
1.2.2 流程说明
  1. spider调度start_request生产任务
  2. start_request下发任务到request_buffer中
  3. spider调度request_buffer批量将任务存储到任务队列数据库中
  4. spider调度collector从任务队列中批量获取任务到内存队列
  5. spider调度parser_control从collector的内存队列中获取任务
  6. parser_control调度request请求数据
  7. request请求与下载数据
  8. request将下载后的数据给response,进一步封装
  9. 将封装好的response返回给parser_control(图示为多个parser_control,表示多线程)
  10. parser_control调度对应的parser,解析返回的response(图示多组parser表示不同的网站解析器)
  11. parser_control将parser解析到的数据item及新产生的request分发到item_bufferrequest_buffer
  12. spider调度item_bufferrequest_buffer将数据批量入库

2 使用

2.1 环境安装

pip install feapder

2.2 创建普通爬虫

feapder create -s xxxx

2.3 爬虫小案例

  1. 下发任务:下发任务
    def start_requests(self):
        yield feapder.Request("https://www.icswb.com/channel-list-channel-161.html")
  1. 解析标题及详情页链接
    def parse(self, request, response):
        # 提取网站title
        print(response.xpath("//title/text()").extract_first())
        # 提取网站描述
        article_list = response.xpath('//ul[@id="NewsListContainer"]/li')
        for i in article_list[:-1]:
            title = i.xpath('./h3/a/text()').extract_first()
            url = i.xpath('./h3/a/@href').extract_first()
            print(url)
            yield feapder.Request('https://www.icswb.com'+url, callback=self.parser_detail, title=title)
  1. 抓详情需要将列表采集到的url作为新任务,然后请求,解析。写法很简单,代码如下:
    def parser_detail(self, request, response):
        title = request.title
        content = response.xpath('//article[@class="am-article"]').extract_first()
        print(content)
  1. 启动代码
if __name__ == "__main__":
    # 开启多线程方式
    SpiderTest(thread_count=5).start()
2.3.1 数据入库

feapder给我们提供了方法

from feapder.db.mysqldb import MysqlDB
db = MysqlDB(
    ip="localhost", port=3306, db="feapder", user_name="feapder", user_pass="feapder123"
)
tt = '胖双'
cc = '胖娜'
sql = "insert ignore into xxx (title,content) values ('%s', '%s')" % (tt,cc)
db.add(sql)

1、创建配置文件

feapder create --setting
  • 激活配置

    # # MYSQL
    MYSQL_IP = "localhost"
    MYSQL_PORT = 3306
    MYSQL_DB = "test8"
    MYSQL_USER_NAME = "root"
    MYSQL_USER_PASS = ""
    

2、创建item文件

feapder create -i xxx     #   xxx=表名

3、项目引入

from xxx_item import XxxItem
   ....
item = XxxItem()
item.title = titles
item.content = contents
yield item

注:数据是一次入库,降低对数据库的操作

2.4 自定义中间件

  • 请求前会经过这个函数
import feapder

class Demo1s(feapder.AirSpider):
    def start_requests(self):
        for i in range(1, 3):
            yield feapder.Request("http://httpbin.org/get",download_midware=self.xxx)

    def xxx(self, request):
        """
        我是自定义的下载中间件 
        :param request:
        :return:
        """
        request.headers = {'User-Agent': "lalala"}
        return request

    def parse(self, request, response):
        print(response.text)
            # print(title, url)

2.5 自定义下载器

自定义下载器即在下载中间件里下载,然后返回response即可,如使用httpx库下载以便支持http2

import feapder, httpx

class Demo1s(feapder.AirSpider):

    def start_requests(self):
        yield feapder.Request("https://www.smartbackgroundchecks.com/")

    def download_midware(self, request):
        with httpx.Client(http2=True) as client:
            response = client.get(request.url)
            response.status_code = 200
        return request, response

    def parse(self, request, response):
        print(response.text)

    def validate(self, request, response):
        if response.status_code != 200:
            raise Exception("response code not 200")  # 重试


if __name__ == "__main__":
    Demo1s(thread_count=1).start()

2.6 对接自动化

​ 采集动态页面时(Ajax渲染的页面),常用的有两种方案。一种是找接口拼参数,这种方式比较复杂但效率高,需要一定的爬虫功底;另外一种是采用浏览器渲染的方式,直接获取源码,简单方便

内置浏览器渲染支持 CHROMEPHANTOMJSFIREFOX

使用方式:

def start_requests(self):
    yield feapder.Request("https://news.qq.com/", render=True)

在返回的Request中传递render=True即可

注意:框架用的是老版本,需要降低现有版本

2.6.1 配置
  • 在setting里面改配置
  • 在项目里面重写配置
__custom_setting__ = dict(
        WEBDRIVER=dict(
            pool_size=1,  # 浏览器的数量
            load_images=True,  # 是否加载图片
            user_agent=None,  # 字符串 或 无参函数,返回值为user_agent
            proxy=None,  # xxx.xxx.xxx.xxx:xxxx 或 无参函数,返回值为代理地址
            headless=False,  # 是否为无头浏览器
            driver_type="CHROME",  # CHROME、PHANTOMJS、FIREFOX
            timeout=30,  # 请求超时时间
            window_size=(1024, 800),  # 窗口大小
            executable_path='D:\software\inters\python3\chromedriver.exe',  # 浏览器路径,默认为默认路径
            render_time=0,  # 渲染时长,即打开网页等待指定时间后再获取源码
            custom_argument=["--ignore-certificate-errors"],  # 自定义浏览器渲染参数
        )
    )

自动适配浏览器驱动

WEBDRIVER = dict(
    ...
    auto_install_driver=True
)

即浏览器渲染相关配置 auto_install_driver 设置True,让其自动对比驱动版本,版本不符或驱动不存在时自动下载

2.6.2 操作浏览器对象
  • 通过 response.browser 获取浏览器对象
import time

import feapder
from feapder.utils.webdriver import WebDriver


class TestRender(feapder.AirSpider):
    def start_requests(self):
        yield feapder.Request("http://www.baidu.com", render=True)

    def parse(self, request, response):
        browser: WebDriver = response.browser
        browser.find_element_by_id("kw").send_keys("feapder")
        browser.find_element_by_id("su").click()
        time.sleep(5)
        print(browser.page_source)

        # response也是可以正常使用的
        # response.xpath("//title")

        # 若有滚动,可通过如下方式更新response,使其加载滚动后的内容
        # response.text = browser.page_source

if __name__ == "__main__":
    TestRender().start()
2.6.3 拦截XHR 数据
WEBDRIVER = dict(
    ...
    xhr_url_regexes=[
        "接口1正则",
        "接口2正则",
    ]
)

数据获取

browser: WebDriver = response.browser
# 提取文本
text = browser.xhr_text("接口1正则")
# 提取json
data = browser.xhr_json("接口1正则")

获取对象

browser: WebDriver = response.browser
xhr_response = browser.xhr_response("接口1正则")
print("请求接口", xhr_response.request.url)
print("请求头", xhr_response.request.headers)
print("请求体", xhr_response.request.data)
print("返回头", xhr_response.headers)
print("返回地址", xhr_response.url)
print("返回内容", xhr_response.content)
2.6.4 示例代码
# -*- coding: utf-8 -*-
"""
Created on 2022-09-13 21:39:47
---------
@summary:
---------
@author: XL
"""
import time

import feapder
from feapder.utils.webdriver import WebDriver
from feapder.network.downloader import SeleniumDownloader

class Test(feapder.AirSpider):
    __custom_setting__ = dict(
        WEBDRIVER=dict(
            pool_size=1,  # 浏览器的数量
            load_images=True,  # 是否加载图片
            user_agent=None,  # 字符串 或 无参函数,返回值为user_agent
            proxy=None,  # xxx.xxx.xxx.xxx:xxxx 或 无参函数,返回值为代理地址
            headless=False,  # 是否为无头浏览器
            driver_type="CHROME",  # CHROME、PHANTOMJS、FIREFOX
            timeout=30,  # 请求超时时间
            window_size=(1024, 800),  # 窗口大小
            executable_path='D:\software\inters\python3\chromedriver.exe',  # 浏览器路径,默认为默认路径
            render_time=0,  # 渲染时长,即打开网页等待指定时间后再获取源码
            custom_argument=["--ignore-certificate-errors"],  # 自定义浏览器渲染参数

            xhr_url_regexes=[
                "/api/post/Query",
            ],  # 拦截 https://careers.tencent.com/tencentcareer/api/post/Query 接口

        )
    )

    def start_requests(self):
        yield feapder.Request("https://careers.tencent.com/search.html?keyword=python",render=True)

    def parse(self, request, response):
        # print(response.text)
        # 可以使用自动化提取
        # 数据提取第一种 使用xpath提取
        # title = response.xpath('//h4[@class="recruit-title"]/text()')
        # print(title)
        # 数据提取第2种 继续使用自动化
        # browser: WebDriver = response.browser
        # time.sleep(3)
        # title = browser.find_element_by_class_name('recruit-title').text
        # print(title)
        # 数据提取第3种,使用API接口拦截
        browser: WebDriver = response.browser
        time.sleep(1.5)
        # 获取接口数据 文本类型
        # data = browser.xhr_text("/api/post/Query")
        # print(data)
        # data = browser.xhr_json("/api/post/Query")
        # print(data)
        # 自定义提取数据
        xhr_response = browser.xhr_response("/api/post/Query")
        print("请求头", xhr_response.request.headers)
        print("请求体", xhr_response.request.data)
        print("返回内容", xhr_response.content)
        # 关闭浏览器
        response.close_browser(request)

if __name__ == "__main__":
    Test().start()

3 项目搭建

3.1 创建项目

feapder create -p <project_name>

image

  • items: 文件夹存放与数据库表映射的item
  • spiders: 文件夹存放爬虫脚本
  • main.py: 运行入口
  • setting.py: 爬虫配置文件

3.2 创建普通爬虫

  • 需要先切换到spiders文件夹下
feapder create -s first_spider

3.3 配置自动化

WEBDRIVER = dict(
    pool_size=1,  # 浏览器的数量
    load_images=True,  # 是否加载图片
    user_agent=None,  # 字符串 或 无参函数,返回值为user_agent
    proxy=None,  # xxx.xxx.xxx.xxx:xxxx 或 无参函数,返回值为代理地址
    headless=False,  # 是否为无头浏览器
    driver_type="CHROME",  # CHROME、PHANTOMJS、FIREFOX
    timeout=30,  # 请求超时时间
    window_size=(1024, 800),  # 窗口大小
    executable_path='D:\software\inters\python3\chromedriver.exe',  # 浏览器路径,默认为默认路径
    render_time=0,  # 渲染时长,即打开网页等待指定时间后再获取源码
    custom_argument=[
        "--ignore-certificate-errors",
        "--disable-blink-features=AutomationControlled",
    ],  # 自定义浏览器渲染参数
    xhr_url_regexes=[
        "/hbsearch/HotelSearch",
    ],
    auto_install_driver=False,  # 自动下载浏览器驱动 支持chrome 和 firefox
    use_stealth_js=True,  # 使用stealth.min.js隐藏浏览器特征

)

3.4 业务逻辑分析

3.5.1 翻页
def next_page(self,browser):
    # 11568
    for x in range(1, 11):
    time.sleep(0.3)
    js = "window.scrollTo(0, {})".format(x*1000) # 1000 2000 3000 4000 5000
    browser.execute_script(js)
    time.sleep(2)
3.5.2 点击
def get_next_page(self,browser):
    try:
        next_btn = browser.find_element_by_xpath('.//a[@class="pn-next"]')      # 下一页地址
        if 'next-disabled' in next_btn.get_attribute('class'):
            print('没有下一页,抓取完成')
        else:
            next_btn.click()
	except Exception as e:
         print(e)

3.5 数据解析

  		browser: WebDriver = response.browser
        time.sleep(1.5)
        self.next_page(browser)
        res = browser.page_source
        html = etree.HTML(res)
        page = ''.join(html.xpath('//span[@class="p-num"]/a[@class="curr"]/text()'))  # 提取当前页面
        print(f'正在采集第{page}个页面')
        goods_ids = html.xpath(
            './/ul[@class="gl-warp clearfix"]/li[@class="gl-item"]/@data-sku')  # 获取商品id号     作为页面的id号
        goods_names_tag = html.xpath('.//div[@class="p-name p-name-type-2"]/a/em')  # 提取商品标题
        goods_prices = html.xpath('.//div[@class="p-price"]//i')  # 提取商品价格
        goods_stores_tag = html.xpath('.//div[@class="p-shop"]')  # 提取店铺
        goods_commits = html.xpath('.//div[@class="p-commit"]//a')  # 提取评价
        goods_names = []
        for goods_name in goods_names_tag:
            goods_names.append(goods_name.xpath('string(.)').strip())
        goods_stores = []
        for goods_store in goods_stores_tag:
            goods_stores.append(goods_store.xpath('string(.)').strip())
        goods_price = []
        for price in goods_prices:
            goods_price.append(price.xpath('string(.)').strip())
        goods_commit = []
        for commit in goods_commits:
            goods_commit.append(commit.xpath('string(.)').strip())

3.6 数据入库

更改配置文件

  • 放开mongo注释
  • 放开管道mongo注释
from feapder import Item
	...
item = Item()  # 声明一个item
item.table_name = "test_mongo"  # 指定存储的表名
item.ID = goods_ids[i]
item.标题 = goods_names[i]
item.价格 =goods_price[i]
item.店铺 = goods_stores[i]
yield item
posted @ 2022-10-07 20:21  尘世风  阅读(917)  评论(0编辑  收藏  举报
*/