Python> 正文

scrapy使用的一些笔记

2024-07-13T21:21:33+08:00

  记录scrapy使用的一些笔记。

1、设置BASE_DIR:

import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))

2、使用、调整或关闭日志:

使用级别记录消息:

import logging

logging.warning("This is a warning") #快速使用

logging.log(logging.WARNING, "This is a warning") #通用方法将给定级别作为参数

# 使用不同的记录器
logger = logging.getLogger("mycustomlogger") #自定义Python记录器
logger = logging.getLogger(__name__) #当前模块
logger.warning("This is a warning")

#Scrapy在每个Spider实例中提供了一个logger,可以像这样访问和使用:
import scrapy

class MySpider(scrapy.Spider):
    name = "myspider"
    start_urls = ["https://scrapy.org"]

    def parse(self, response):
        self.logger.info("Parse function called on %s", response.url)

# 将日志重定向到文件
logging.basicConfig(
    filename="log.txt", format="%(levelname)s: %(message)s", level=logging.INFO
)

日志配置:

  • LOG_FILE # 日志消息的目的地
  • LOG_FILE_APPEND #日志消息将被重定向到名为LOG_FILE的文件,其编码为LOG_ENCODING,如果未设置并且LOG_ENABLED为True,则将其写入标准输出。
  • LOG_ENABLED # 如果LOG_ENABLED是False,则不会有任何可见的日志输出。
  • LOG_ENCODING #指定日志消息的编码。

  • LOG_LEVEL # 日志级别,确定要显示的最低严重性级别,严重性较低的消息将被过滤掉。

  • LOG_FORMAT #指定日志格式。

  • LOG_DATEFORMAT #指定日志格式。

  • LOG_STDOUT # 设置为True,则将标准输出路由到日志器。

  • LOG_SHORT_NAMES # 如果设置了LOG_SHORT_NAMES,则日志将不会显示打印日志的Scrapy组件

自定义日志格式:

需要在项目的 settings.py 文件中设置 LOG_FORMAT 和 LOG_DATEFORMAT 选项

# settings.py

# 日志文件名
LOG_FILE = 'scrapy.log'

# 日志级别 (DEBUG/INFO/WARNING/ERROR/CRITICAL)
LOG_LEVEL = 'INFO'

# 日志格式
LOG_FORMAT = '%(levelname)s: [%(asctime)s] [%(name)s:%(lineno)d] %(message)s'

# 时间格式
LOG_DATEFORMAT = '%Y-%m-%d %H:%M:%S'

这里的 LOG_FORMAT 定义了日志消息的格式:

  • %levelname% 显示日志级别的名称。
  • [%(asctime)s] 显示时间戳。
  • [%(name)s:%(lineno)d] 显示记录器的名字和日志消息所在的行号。
  • %(message)s 显示日志消息本身。
  • LOG_DATEFORMAT 定义了日期和时间的格式。

3、在不创建项目的情况下运行Spider:

scrapy runspider my_spider.py

4、查看从Scrapy发送和接收的cookie:

setting中启用COOKIES_DEBUG设置

5、从脚本运行Scrapy:

import scrapy
from scrapy.crawler import CrawlerProcess


class MySpider(scrapy.Spider):
    # Your spider definition
    ...


process = CrawlerProcess(
    settings={
        "FEEDS": {
            "items.json": {"format": "json"},
        },
    }
)

process.crawl(MySpider)
process.start()  # the script will block here until the crawling is finished

6、暂停、恢复crawl

要启动启用持久性支持的蜘蛛,请像这样运行它:

scrapy crawl somespider -s JOBDIR=crawls/somespider-1

然后,可以随时安全地停止蜘蛛(通过按Ctrl-C或发送信号),然后通过发出相同的命令恢复它

7、xpath提取文本数据

CSS选择器和xpath选择器可以混合使用。

get():始终返回单个结果;如果有多个匹配项,则返回第一个匹配项的内容,如果没有匹配项,则返回None。get()与extract_first()相同。 getall():返回包含所有结果的列表。getall()与extract()相同。

response.xpath("//title/text()").getall()
['Example website']
response.xpath("//title/text()").get()
'Example website'

# 如果未找到任何元素则返回
response.xpath('//div[@id="not-exists"]/text()').get() is None
True
可以提供默认返回值作为参数以代替
response.xpath('//div[@id="not-exists"]/text()').get(default="not-found")
'not-found'

# 获取属性值选择器的.attrib
[img.attrib["src"] for img in response.css("img")]
['image1_thumb.jpg',
'image2_thumb.jpg',
'image3_thumb.jpg',
'image4_thumb.jpg',
'image5_thumb.jpg']

# .attrib属性也可以直接在SelectorList上使用它返回第一个匹配元素的属性
# 当只需要一个结果时这是最有用的方法
response.css("img").attrib["src"]
'image1_thumb.jpg'

8、xpath注意点:

//node[1]选择在各自父节点下首先出现的所有节点
(//node)[1]选择文档中的所有节点然后只获取其中的第一个

from scrapy import Selector
sel = Selector(
    text="""
    <ul class="list">
        <li>1</li>
        <li>2</li>
        <li>3</li>
    </ul>
    <ul class="list">
        <li>4</li>
        <li>5</li>
        <li>6</li>
    </ul>"""
)
xp = lambda x: sel.xpath(x).getall()

xp("//li[1]")
['<li>1</li>', '<li>4</li>']

xp("//ul/li[1]")
['<li>1</li>', '<li>4</li>']

xp("(//li)[1]")
['<li>1</li>']

xp("(//ul/li)[1]")
['<li>1</li>']

选择文本节点:

from scrapy import Selector
sel = Selector(
    text='<a href="#">Click here to go to the <strong>Next Page</strong></a>'
)

sel.xpath("//a//text()").getall()  # take a peek at the node-set
['Click here to go to the ', 'Next Page']
sel.xpath("string(//a[1]//text())").getall()  # convert it to string
['Click here to go to the ']

#节点将自身的文本及其所有后代放在一起
sel.xpath("//a[1]").getall()  # select the first node
['<a href="#">Click here to go to the <strong>Next Page</strong></a>']
sel.xpath("string(//a[1])").getall()  # convert it to string
['Click here to go to the Next Page']

XPath表达式中的变量:

调用时,所有变量引用都必须具有绑定值(否则您将获得异常)

# 根据其id属性值匹配元素的示例而无需对其进行硬编码
response.xpath("//div[@id=$val]/a/text()", val="images").get()
'Name: My image 1 '

# 查找包含5个child的标签的id属性
response.xpath("//div[count(a)=$cnt]/@id", cnt=5).get()
'images'

9、Pipeline

Pipeline的典型用途是: 验证抓取的数据(检查项目是否包含某些字段) 检查重复项(并删除它们) 将抓取的项目存储在数据库中

每个Pipeline都是一个Python类,必须实现以下方法: process_item:必须:返回item对象,返回Deferred或引发DropItem异常。 -- Item:Spider爬虫中yield出来的item对象 -- spider:是Spider对象,如果一个工程中有多个爬虫就需要使用spider.name来判断当前的爬虫

Pipeline还可以实现以下方法: open_spider:代表开启爬虫的时候执行的方法(常用于开启数据库连接,开启文件写入)。 close_spider:代表关闭爬虫的时候执行的方法(常用于关闭数据库,关闭文件写入)。 from_crawler:定义一个类方法(主要使用Scrapy的核心组件,比如settings、singals)

写入到mysql数据库: 在settings.py中设置mysql数据库连接信息

MYSQL_HOST = 'localhost'
MYSQL_DBNAME = 'py_test'  # 数据库名字,请修改
MYSQL_USER = 'root'  # 数据库账号,请修改
MYSQL_PASSWD = 'root'  # 数据库密码,请修改
MYSQL_PORT = 3306  # 数据库端口,在dbhelper中使用
import pymysql

class XiciPipeline(object):
    def __init__(self, dbparams):
        self.connect = pymysql.connect(
            host=dbparams['host'],
            port=dbparams['port'],
            db=dbparams['db'],
            user=dbparams['user'],
            passwd=dbparams['passwd'],
            charset=dbparams['charset'],
            use_unicode=dbparams['use_unicode']
        )
        # 创建一个句柄
        self.cursor = self.connect.cursor()

    @classmethod
    def from_crawler(cls, crawler):
        # 读取settings中的配置
        dbparams = dict(
            host=crawler.settings.get('MYSQL_HOST'),
            db=crawler.settings.get('MYSQL_DBNAME'),
            user=crawler.settings.get('MYSQL_USER'),
            passwd=crawler.settings.get('MYSQL_PASSWD'),
            port=crawler.settings.get('MYSQL_POR'),
            charset='utf8',  # 编码要加上,否则可能出现中文乱码问题
            use_unicode=False,
        )
        return cls(dbparams)

    def process_item(self, item, spider):
        if spider.name == 'xici':
            # sql语句:插入数据
            sql = 'insert into xici(ip, port, speed, proxy_type, localhost) values (%s, %s, %s, %s,%s)'
            self.cursor.execute(sql, (item['ip'], item['port'], item['speed'], item['proxy_type'], item['localhost']))
            self.connect.commit()
        return item

    def close_spider(self, spider):
        self.connect.close()

写入到mongoDB数据库:

import pymongo
from itemadapter import ItemAdapter


class MongoPipeline:
    collection_name = "scrapy_items"

    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get("MONGO_URI"),
            mongo_db=crawler.settings.get("MONGO_DATABASE", "items"),
        )

    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]

    def close_spider(self, spider):
        self.client.close()

    def process_item(self, item, spider):
        self.db[self.collection_name].insert_one(ItemAdapter(item).asdict())
        return item

10、使用meta在不同的请求中传递参数

在Request()中直接使用meta传递参数到下一个请求中 在下一个请求中使用response.meta.get(字段名, '')获取上一个请传递过来的参数

11、Requests与Response的认识

Requests的认识: 定义:在前面的章节中我们介绍了使用Requests建立连续性爬虫(我们爬取一页数据需要重新发送一个请求的时候触发的),这个类需要传递一些参数. 导包方式:from scrapy.http import Request 使用方式:yield Request(url='', callback=''...) Request的主要参数介绍:

class Request(object_ref):

    def __init__(self, url, callback=None, method='GET', headers=None, body=None,
                 cookies=None, meta=None, encoding='utf-8', priority=0,
                 dont_filter=False, errback=None, flags=None):

url: 字符串类型url地址
callback:回调函数名称
method:字符串类型请求方式,如果GET,POST
headers:字典类型的,浏览器用户代理
cookies:设置cookies
meta:字典类型键值对,向回调函数直接传一个指定值
encoding:设置网页编码
priority:默认为0,如果设置的越高,越优先调度
dont_filter:默认为False,如果设置为真,会过滤掉当前url
errback: 在发生错误的时候执行的函数

Response的认识:

定义:Response对象一般是由Scrapy给你自定构建的.因此开发者不需要关心如何创建Response对象,而是直接知道他有哪些属性就可以。主要包括下面这些常用属性:

meta:从上一个请求传递过来的,常用于多个请求之间数据交互
encoding: 返回当前字符串编码和解码的格式
text: 将返回的数据作为unicode字符串返回
body:将返回的数据作为bytes字符串返回
xpath:使用xpath选择器
css: 使用css选择器

12、抓取分页文章合并数据

很多网站为了获取 pv, 会把一篇完整的文章分成多页, 这也给爬虫抓取造成了一些小问题。常规的做法是先解析第一页数据,保存到 item , 再检测是否存在下一页, 如果存在,重复执行抓取函数, 直到到最后一页停止。 但是在scrapy 中有一个问题, scrapy 中的回调函数无法返回值,抓取的文章正文无法返回, 所以这里可以用 request 的 meta 属性单向传递item变量到抓取函数中。

# 取第一页的文章正文 并且解析分成多页页面信息的操作
def parse_article(self, response):
        """parse article content

        Arguments:
            response {[type]} -- [description]
        """
        selector = Selector(response)
        article = ArticleItem()
        # 先获取第一页的文章正文
        article['content'] = selector.xpath(
            '//article[@class="article-content"]').get()

        # 拆分最后一页的 url, 可以得到文章的base url 和总页数
        page_end_url = selector.xpath(
            '//div[@class="pagination"]/ul/li/a/@href').extract()[-1]
        page_base_url, page_count = re.findall(
            '(.*?)_(.*?).html', page_end_url)[0]
        page_count = int(page_count)

        for page in range(2, page_count + 1):
            # 构造出分页 url
            url = page_base_url + '_{}.html'.format(page)
            # 手动生成 Request 请求 注意函数中的 priority 参数 代表了 Request 是按顺序执行的 值越大 优先级越高
            request = Request(url=url, callback=self.parse_content, priority=-page)
            #  self.parse_content 函数传递 item 变量
            request.meta['article'] = article
            yield request
# 取文章正文 并存入 item 的操作
def parse_content(self, response):
        selector = Selector(response)
        article = response.meta['article']
        # 注意这里对 article['content']的操作是 += 这样不会清空上一页保存的数据
        article['content'] += selector.xpath('//article[@class="article-content"]').get()
        yield article

13、Pycharm中调试scrapy爬虫

1、使用scrapy.cmdline的execute方法调试:

首先,在项目文件scrapy.cfg的同级建立main.py文件(注意,必须是同级建立),在其中键入如下代码:

from scrapy.cmdline import execute
import sys
import os

sys.path.append(os.path.dirname(os.path.abspath(__file__)))

execute(['scrapy', 'crawl', 'spider_name'])  # 你需要将此处的spider_name替换为你自己的爬虫名称

在其余爬虫文件中设置断点后,运行(debug)main.py,即可实现在pycharm中的调试。

2、使用scrapy的CrawlerProcess方法调试:

在项目文件scrapy.cfg的同级建立main.py文件(注意,必须是同级建立),在其中键入如下代码:

from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings

if __name__ == '__main__':
    process = CrawlerProcess(get_project_settings())
    process.crawl('spider_name')    #  你需要将此处的spider_name替换为你自己的爬虫名称
    process.start()

在其余爬虫文件中设置断点后,运行(debug)main.py,即可实现在pycharm中的调试。 两种方式都很简单实用,值得掌握。

14、Scrapy导入item类报错:NameError: name is not defined

修改为:

try:
    from xxx.items import MovieItem
except:
    from  ..items import MovieItem
分享到:

Ranvane的日常记录

关于我们 客服中心 广告服务 法律声明