Python爬虫框架Scrapy学习笔记原创

scrapy

[TOC]

开始

scrapy安装

  1. 首先手动安装windows版本的Twisted

    https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted

    pip install Twisted-18.4.0-cp36-cp36m-win_amd64.whl

  2. 安装scrapy

    pip install -i https://pypi.douban.com/simple/ scrapy

  3. windows系统额外需要安装pypiwin32

    pip install -i https://pypi.douban.com/simple pypiwin32

新建项目

开始一个项目

扫描二维码关注公众号,回复: 2173880 查看本文章
E:\svnProject> scrapy startproject TestSpider

生成一个新的爬虫(generate)

E:\svnProject> cd TestSpider E:\svnProject\TestSpider> scrapy genspider dongfeng www.dongfe.com

启动一个爬虫

E:\svnProject\TestSpider> scrapy crawl dongfeng

SHELL模式

> scrapy shell http://www.dongfe.com/ # 命令行调试该网页

pycharm调试启动文件

E:\svnProject\TestSpider> vim main.py import sys import os from scrapy.cmdline import execute BASE_DIR = os.path.dirname(os.path.abspath(__file__)) sys.path.append(BASE_DIR) # scrapy crawl dongfeng execute(["scrapy", "crawl", "dongfeng"])

项目基本配置

E:\svnProject\TestSpider\TestSpider> vim settings.py ROBOTSTXT_OBEY = False # 不要遵循网站robots文件

XPATH

表达式 说明
/body 选出当前选择器的根元素body
/body/div 选取当前选择器文档的根元素body的所有div子元素
/body/div[1] 选取body根元素下面第一个div子元素
/body/div[last()] 选取body根元素下面最后一个div子元素
/body/div[last()-1] 选取body根元素下面倒数第二个div子元素
//div 选取所有div子元素(不论出现在文档任何地方)
body//div 选取所有属于body元素的后代的div元素(不论出现在body下的任何地方)
/body/@id 选取当前选择器文档的根元素body的id属性
//@class 选取所有元素的class属性
//div[@class] 选取所有拥有class属性的div元素
//div[@class='bold'] 选取所有class属性等于bold的div元素
//div[contains(@class,'bold')] 选取所有class属性包含bold的div元素
/div/* 选取当前文档根元素div的所有子元素
//* 选取文档所有节点
//div[@*] 获取所有带属性的div元素
//div/a | //div/p 选取所有div元素下面的子元素a和子元素p(并集)
//p[@id='content']/text() 选取id为content的p标签的内容(子元素的标签和内容都不会获取到)

> 注意: XPATH在选择时,参考的是HTML源码,而不是JS加载后的HTML代码

操作例子

title_selector = response.xpath("//div[@class='entry-header']/h1/text()") title_str = title_selector.extract()[0]

CSS选择器

表达式 说明
* 选择所有节点
#container 选择Id为container的节点
.container 选取所有包含container类的节点
li a 选取所有li下的所有后代a元素(子和孙等所有的都会选中)
ul + p 选取ul后面的第一个相邻兄弟p元素
div#container > ul 选取id为container的div的所有ul子元素
ul ~ p 选取与ul元素后面的所有兄弟p元素
a[title] 选取所有有title属性的a元素
a[href='http://taobao.com'] 选取所有href属性等于http://taobao.com的a元素
a[href*='taobao'] 选取所有href属性包含taobao的a元素
a[href^='http'] 选取所有href属性开头为http的a元素
a[href$='.com'] 选取所有href属性结尾为.com的a元素
input[type=radio]:checked 选取选中的radio的input元素
div:not(#container) 选取所有id非container的div元素
li:nth-child(3) 选取第三个li元素
tr:nth-child(2n) 选取偶数位的tr元素
a::attr(href) 获取所有a元素的href属性值

操作例子

h1_selector = response.css(".container h1::text") # 选取h1标题的内容 h1_str = h1_selector.extract_first() # 取出数组第一个,如果没有为空

爬虫

爬取某网站文章列表例子

>>> vim ArticleSpider/spiders/jobbole.py import scrapy from scrapy.http import Request from urllib import parse import re from ArticleSpider.items import ArticleItem from ArticleSpider.utils.common import get_md5 # url转md5 class JobboleSpider(scrapy.Spider): name = 'jobbole' allowed_domains = ['blog.jobbole.com'] start_urls = ['http://blog.jobbole.com/all-posts/'] def parse(self, response): """ 文章列表页的文章链接解析 :param response: :return: """ css = "#archive > .post > .post-thumb > a" article_urls_selector = response.css(css) # 获取当前列表页所有文章的链接 for article_url_selector in article_urls_selector: head_img_url = article_url_selector.css("img::attr(src)").extract_first() # 封面URL head_img_full_url = parse.urljoin(response.url, head_img_url) # 封面图片完整URL article_url = article_url_selector.css("a::attr(href)").extract_first("") # 文章URL article_full_url = parse.urljoin(response.url, article_url) # 智能的拼接URL,相对地址直接对接;绝对地址只取出域名对接;完全地址不对接,直接获取。 yield Request(url=article_full_url, callback=self.article_parse, meta={"head_img_full_url": head_img_full_url}) # 请求文章详情页并设置回调函数解析内容和meta传参 next_url = response.css(".next.page-numbers::attr(href)").extract_first("") if next_url: yield Request(url=parse.urljoin(response.url, next_url), callback=self.parse) # 下一页文章列表使用递归 def article_parse(self, response): """ 文章详情页的内容解析 :param response: :return: """ title = response.css(".grid-8 .entry-header > h1::text").extract_first("") # 标题内容 add_time = response.css(".grid-8 .entry-meta p::text").extract_first("") add_time_match = re.match("[\s\S]*?(\d{2,4}[/-]\d{1,2}[/-]\d{1,2})[\s\S]*", add_time) if add_time_match: add_time = add_time_match.group(1) else: add_time = add_time.strip() content = response.css(".grid-8 .entry").extract_first("") # 文章内容 star = response.css("h10::text").extract_first("") # 点赞数 head_img_url = response.meta.get("head_img_full_url") # 封面URL,通过上一个解释器在回调时传参得到的数据 # 把数据整理到item article_item = ArticleItem() # 实例化一个item article_item["title"] = title article_item["content"] = content # 把时间字符串转为可保存mysql的日期对象 try: add_time = datetime.datetime.strptime(add_time, "%Y/%m/%d").date() except Exception as e: add_time = datetime.datetime.now().date() article_item["add_time"] = add_time article_item["star"] = star article_item["head_img_url"] = [head_img_url] # 传递URL图片保存列表供ImagesPipeline使用 article_item["url"] = response.url article_item["url_object_id"] = get_md5(response.url) # 获取url的md5值 yield article_item # 传递到pipeline。请看settings.py中ITEM_PIPELINES字典
# Item设计,类似于django的表单类
>>> vim ArticleSpider/items.py import scrapy class ArticleItem(scrapy.Item): title = scrapy.Field() # 标题 content = scrapy.Field() # 内容 add_time = scrapy.Field() # 文章添加时间 url = scrapy.Field() # 文章URL url_object_id = scrapy.Field() # URL的MD5值 head_img_url = scrapy.Field() # 封面图URL head_img_path = scrapy.Field() # 封面图本地路径 star = scrapy.Field() # 点赞数
>>> vim ArticleSpider/spiders/settings.py # 修改配置文件,去掉这个地方的注释,当爬虫解析函数返回Item对象时,需要经过这个管道 # Item管道 ITEM_PIPELINES = { # item的pipeline处理类;类似于item中间件 'ArticleSpider.pipelines.ArticlespiderPipeline': 300, # 处理顺序是按数字顺序,1代表第一个处理 }
# URL转md5函数
>>> create ArticleSpider/utils/__init__.py # 公共工具包 >>> vim common.py import hashlib def get_md5(url): # 获取URL的MD5值 if isinstance(url, str): # 如果是Unicode字符串 url = url.encode("utf-8") m = hashlib.md5() m.update(url) # 只接受UTF-8字节码 return m.hexdigest()

图片自动下载

>>> vim ArticleSpider/settings.py PROJECT_DIR = os.path.join(BASE_DIR, "ArticleSpider") # 修改配置文件,去掉这个地方的注释,当爬虫解析函数返回Item对象时,需要经过这个管道 # Item管道 ITEM_PIPELINES = { # item的pipeline处理类;类似于item中间件 'ArticleSpider.pipelines.ArticlespiderPipeline': 300, # 处理顺序是按数字顺序,1代表第一个处理 'scrapy.pipelines.images.ImagesPipeline': 1, } IMAGES_URLS_FIELD = "head_img_url" # 图片URL的字段名 IMAGES_STORE = os.path.join(PROJECT_DIR, "images") # 图片本地保存地址 # IMAGES_MIN_HEIGHT = 100 # 接收图片的最小高度 # IMAGES_MIN_WIDTH = 100 # 接收图片的最小宽度

图片自动下载自定义类

>>> vim ArticleSpider/settings.py ITEM_PIPELINES = { ... #'scrapy.pipelines.images.ImagesPipeline': 1, 'ArticleSpider.pipelines.ArticleImagePipeline': 1, }
>>> vim ArticleSpider/pipelines.py class ArticleImagePipeline(ImagesPipeline): def item_completed(self, results, item, info): if "head_img_url" in item: # 只处理有数据的URL for ok, value in results: # 默认是多个图片URL,其实只传递了一个,所以results内只有一个 image_file_path = value["path"] # 获取图片保存的本地路径 item["head_img_path"] = image_file_path return item # 返回item,下一个pipeline接收处理

数据保存

把item数据导出到json文件中

vim ArticleSpider/pipelines.py import codecs # 文件操作模块 import json # 把item保存到json文件 class JsonWithEncodingPipeline(object): def __init__(self): self.file = codecs.open("article.json", 'w', encoding="utf-8") def process_item(self, item, spider): lines = json.dumps(dict(item), ensure_ascii=False) + "\n" # 关闭ascii保存,因为有中文 self.file.write(lines) return item def spider_closed(self, spider): # 当爬虫关闭时 self.file.close()

注册到item管道配置中


vim ArticleSpider/settings.py ITEM_PIPELINES = { '...', 'ArticleSpider.pipelines.JsonWithEncodingPipeline': 2, }

使用自带的模块导出json文件

>>> vim ArticleSpider/pipelines.py from scrapy.exporters import JsonItemExporter class JsonExporterPipeline(object): # 调用scrapy提供的json export导出json文件 def __init__(self): self.file = open("articleexport.json", "wb") self.exporter = JsonItemExporter(self.file, encoding="utf-8", ensure_ascii=False) self.exporter.start_exporting() def process_item(self, item, spider): self.exporter.export_item(item) return item def close_spider(self, spider): self.exporter.finish_exporting() self.file.close()

注册到item管道配置中

vim ArticleSpider/settings.py ITEM_PIPELINES = { '...', 'ArticleSpider.pipelines.JsonExporterPipeline': 2, }

使用mysql保存

# 安装mysql驱动
>>> pip install mysqlclient # centos需要另外安装驱动 >>> sudo yum install python-devel mysql-devel

使用同步的机制写入mysql

import MySQLdb
class MysqlPipeline(object): def __init__(self): self.conn = MySQLdb.connect('dongfe.com', 'root', 'Xiong123!@#', 'article_spider', charset="utf8", use_unicode=True) self.cursor = self.conn.cursor() def process_item(self, item, spider): insert_sql = """ insert into article(title, url, add_time, star) values (%s, %s, %s, %s) """ self.cursor.execute(insert_sql, (item['title'], item['url'], item['add_time'], item['star'])) self.conn.commit()

使用异步的机制写入mysql

import MySQLdb
import MySQLdb.cursors from twisted.enterprise import adbapi class MysqlTwistedPipline(object): def __init__(self, dbpool): self.dbpool = dbpool @classmethod def from_settings(cls, settings): pass # 这个方法会把settings文件传进来 dbparms = dict( host="dongfe.com", db="article_spider", user="root", passwd="Xiong123!@#", charset="utf8", cursorclass=MySQLdb.cursors.DictCursor, use_unicode=True, ) dbpool = adbapi.ConnectionPool("MySQLdb", **dbparms) return cls(dbpool) def process_item(self, item, spider): # 使用twisted将mysql插入变成异步执行 query = self.dbpool.runInteraction(self.do_insert, item) query.addErrback(self.handle_error) # 处理异常 def do_insert(self, cursor, item): # 执行具体的插入 insert_sql = """ insert into article(title, url, add_time, star) values (%s, %s, %s, %s) """ cursor.execute(insert_sql, (item['title'], item['url'], item['add_time'], item['star'])) def handle_error(self, failure, item, spider): # 处理异步插入的异常 print(failure)

item loader

直接将ItemCSS选择器绑定到一起,直接把选择出来的数据放入Item中。

item loader的一般使用

>>> vim ArticleSpider/spiders/jobbole.py class JobboleSpider(scrapy.Spider): name = 'jobbole' allowed_domains = ['blog.jobbole.com'] start_urls = ['http://blog.jobbole.com/all-posts/'] ... # 文章详情页的内容解析 def article_parse(self, response): # 通过item loader加载item item_loader = ItemLoader(item=ArticleItem(), response=response) item_loader.add_css("title", ".grid-8 .entry-header > h1::text") item_loader.add_css("content", ".grid-8 .entry") item_loader.add_css("add_time", ".grid-8 .entry-meta p::text") item_loader.add_value("url", response.url) item_loader.add_value("url_object_id", get_md5(response.url)) item_loader.add_value("head_img_url", [head_img_url]) item_loader.add_css("star", "h10::text") article_item = item_loader.load_item() yield article_item # 传递到pipeline。请看settings.py中ITEM_PIPELINES字典

> 使用Item Loader的两个问题:
>
> 1. 原始数据需要处理
> 1. 解决办法:在Item内使用字段的处理器
> 2. 不管数据有几个,获取的是一个数组
> 1. 解决办法:在Item内字段处理器中使用TakeFirst()方法

配合item 的processor处理器的使用

>>> vim ArticleSpider/items.py # MapCompose:可以调用多个函数依次运行 # TakeFirst: 与extract_first()函数一样,只选择数组第一个数据 # Join: 把数组用符号连接成字符串,比如Join(",") from scrapy.loader.processors import MapCompose, TakeFirst, Join # add_time键处理函数 def date_convert1(value): add_time_match = re.match("[\s\S]*?(\d{2,4}[/-]\d{1,2}[/-]\d{1,2})[\s\S]*", value) if add_time_match: add_time = add_time_match.group(1) else: add_time = value.strip() return add_time def date_convert2(value): try: add_time = datetime.datetime.strptime(value, "%Y/%m/%d").date() except Exception as e: add_time = datetime.datetime.now().date() return add_time class ArticleItem(scrapy.Item): ... add_time = scrapy.Field( input_processor=MapCompose(date_convert1, date_convert2), # 处理原始数据 output_processor=TakeFirst() # 只取数组中第一个数据 ) # 文章添加时间 ...

自定义item loader

> 可以设置默认的default_output_processor = TakeFirst()

>>> vim ArticleSpider/items.py from scrapy.loader import ItemLoader class ArticleItemLoader(ItemLoader): # 自定义item loader default_output_processor = TakeFirst() # 默认item处理器,什么都不做 def default_processor(value): return value class ArticleItem(scrapy.Item): ... head_img_url = scrapy.Field( # 图像URL需要一个数组类型,不取第一个数据,定义一个默认处理器覆盖掉 # 另外注意在使用sql保存时,需要取出数组第一个 output_processor=default_processor ) # 封面图URL ... ########################################################################################## >>> vim ArticleSpider/spiders/jobbole.py from ArticleSpider.items import ArticleItemLoader class JobboleSpider(scrapy.Spider): name = 'jobbole' allowed_domains = ['blog.jobbole.com'] start_urls = ['http://blog.jobbole.com/all-posts/'] ... # 文章详情页的内容解析厦工叉车 def article_parse(self, response): ... # 通过item loader加载item item_loader = ArticleItemLoader(item=ArticleItem(), response=response) ...

猜你喜欢

转载自www.cnblogs.com/xyou/p/9315959.html