python scrapy爬虫spider抓取详解
2022年5月30日大约 3 分钟约 882 字
爬取某网站文章列表例子
>>> 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接收处理