记录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