scrapy爬取知名技术文章网站
一、scrapy安装以及目录结构介绍
1、安装
pip install -i https://pypi.douban.com/simple/ scrapy
安装Scrapy报错:
Could not find a version that satisfies the requirement Twisted>=13.1.0 (from Scrapy) (from versions: )
No matching distribution found for Twisted>=13.1.0 (from Scrapy)
原因是没有安装Twisted:
进入https://pypi.org/project/Twisted/#files下载Twisted-19.2.1.tar.bz2
tar -xjvf Twisted-19.2.1.tar.bz2
cd Twisted-19.2.1
python setup.py install
成功
2、创建项目
scrapy startproject ArticleSpider
创建模板:
scrapy genspider jobbole blog.jobbole.com
3、项目目录结构
二、pycharm调试scrapy执行流程
1、在目录下创建main.py文件,文件内容如下:
from scrapy.cmdline import execute
import sys
import os
# 获取main.py文件的父目录
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
execute(["scrapy", "crawl", "jobbole"])
2、修改settings.py中ROBOTSTXT_OBEY = True为ROBOTSTXT_OBEY = False
3、选中main.py文件,鼠标右键,选择"Debug main"鼠标左键点击,就可以进入debug调试模式了
成功:
三、xpath用法
1、xpath简介
(1) xpath使用路径表达式在xml和html中进行导航;
(2) xpath包含标准函数库;
(3) xpath是一个w3c的标准。
2、xpath节点关系
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>
(1) 父节点
<head>为<title>的父节点
(2) 子节点
<title>为<head>的子节点
(3) 同胞节点
<title>和<meta>为同胞节点
(4) 先辈节点
<html>和<head>为<title>的父辈节点
(5) 后代节点
<head>和<title>为<html>的后辈节点
3、xpath语法
表达式 | 说明 |
article | 选取所有article元素的所有子节点 |
/article | 选取根元素article |
article/a | 选取所有属于article的子元素的a元素 |
//div | 选取所有div子元素(不论出现在文档任何地方) |
article//div | 选取所有属于article元素的后代的div元素,不管它出现在article之下的任何位置 |
//@class | 选取所有名为class的属性 |
/article/div[1] | 选取属于article子元素的第一个div元素 |
/article/div[last()] | 选取属于article子元素的最后一个div元素 |
/article/div[last()-1] | 选取属于article子元素的倒数第二个div元素 |
//div[@lang] | 选取所有拥有lang属性的div元素 |
//div[@lang='eng'] | 选取所有lang属性为eng的div元素 |
/div/* | 选取属于div元素的所有子节点 |
//* | 选取所有元素 |
//div[@*] | 选取所有带属性的div元素 |
//div/a | //div/p | 选取所有div元素的a和p元素 |
//span | //ul | 选取文档中的span和ul元素 |
article/div/p | //span | 选取所有属于article元素的div元素的p元素 以及 文档中所有的span元素 |
实例:
# -*- coding: utf-8 -*-
import scrapy
import re
class JobboleSpider(scrapy.Spider):
name = 'jobbole'
allowed_domains = ['blog.jobbole.com']
start_urls = ['http://blog.jobbole.com/110287/']
def parse(self, response):
# 标题
title = response.xpath("//*[@id='post-110287']/div[1]/h1/text()").extract()[0]
# 发布日期
create_date = response.xpath("//*[@id='post-110287']/div[2]/p/text()").extract()[0].strip().replace("·","").strip()
# 点赞数
praise_nums = response.xpath("//*[@id='110287votetotal']/text()").extract()[0]
# 收藏数
fav_nums = response.xpath("//*[@id='post-110287']/div[3]/div[8]/span[2]/text()").extract()[0]
match_re = re.match(".*(\d+).*", fav_nums)
if match_re:
fav_nums = match_re.group(1)
# 评论数
comment_nums = response.xpath("//*[@id='post-110287']/div[3]/div[8]/a/span/text()").extract()[0]
match_re = re.match(".*(\d+).*", comment_nums)
if match_re:
comment_nums = match_re.group(1)
# 正文
comment = response.xpath("//div[@class='entry']").extract()[0]
# 标签
tag_list = response.xpath("//*[@id='post-110287']/div[2]/p/a/text()").extract()
tag_list = [element for element in tag_list if not element.strip().endswith("评论")]
tags = ",".join(tag_list)
四、css选择器实现字段解析
1、css选择器
表达式 | 说明 |
* | 选择所有节点 |
#container | 选择id为container的节点 |
.container | 选取所有class包含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://jobbole.com"] | 选取所有href属性为jobbole.com值的a元素 |
a[href*="jobbole"] | 选取所有href属性包含jobbole的a元素 |
a[href^="http"] | 选取所有href属性值以http开头的a元素 |
a[href$=".jpg"] | 选取所有href属性值以.jpg结尾的a元素 |
input[type=radio]:checked | 选择选中的radio元素 |
div:not(#container) | 选取所有id非cintainer的div属性 |
li:nth-child(3) | 选取第三个li元素 |
tr:nth-chlid(2n) | 第偶数个tr |
实例:
import scrapy
import re
class JobboleSpider(scrapy.Spider):
name = 'jobbole'
allowed_domains = ['blog.jobbole.com']
start_urls = ['http://blog.jobbole.com/110287/']
def parse(self, response):
# 通过css选择器获取字段值
# 标题
title = response.css(".entry-header h1::text").extract()[0]
create_date = response.css("p.entry-meta-hide-on-mobile::text").extract()[0].strip().replace("·", "").strip()
# 点赞数
praise_nums = response.css("#110287votetotal::text").extract()[0]
# 收藏数
fav_nums = response.css("span.bookmark-btn::text").extract()[0]
match_re = re.match(".*?(\d+).*", fav_nums)
if match_re:
fav_nums = match_re.group(1)
# 评论数
comment_nums = response.css("a[href='#article-comment'] span::text").extract()[0]
match_re = re.match(".*?(\d+).*", comment_nums)
if match_re:
comment_nums = match_re.group(1)
# 正文
contment = response.css(".entry::text").extract()[0]
# # 标签
tag_list = response.css("p.entry-meta-hide-on-mobile a::text").extract()
tag_list = [element for element in tag_list if not element.strip().endswith("评论")]
tags = ",".join(tag_list)
五、编写spider爬取jobbole的所有文章
# -*- coding: utf-8 -*-
import scrapy
import re
from scrapy.http import Request
from urllib import parse
class JobboleSpider(scrapy.Spider):
name = 'jobbole'
allowed_domains = ['blog.jobbole.com']
start_urls = ['http://blog.jobbole.com/all-posts/']
def parse(self, response):
"""
1、获取文章列表中的文章url并交给scrapy下载后并进行解析;
2、获取下一页的url并交给scrapy进行下载,下载完成后交给parse
"""
# 解析列表页中的所有文章url并交给scrapy下载后进行解析
post_urls = response.css("#archive .floated-thumb .post-thumb a::attr(href)").extract()
for post_url in post_urls:
# 有的href没有带域名,域名+href:response.url + post_url
yield Request(url=parse.urljoin(response.url, post_url), callback=self.parse_detail)
# 提取下一页并交给scrapy进行下载
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 parse_detail(self, response):
# 提取文章的具体字段
# 通过css选择器获取字段值
# 标题
title = response.css(".entry-header h1::text").extract_first("")
create_date = response.css("p.entry-meta-hide-on-mobile::text").extract_first("").strip().replace("·", "").strip()
# 点赞数
praise_nums = response.css(".vote-post-up h10::text").extract_first("")
# 收藏数
fav_nums = response.css("span.bookmark-btn::text").extract_first("")
match_re = re.match(".*?(\d+).*", fav_nums)
if match_re:
fav_nums = int(match_re.group(1))
else:
fav_nums = 0
# 评论数
comment_nums = response.css("a[href='#article-comment'] span::text").extract_first("")
match_re = re.match(".*?(\d+).*", comment_nums)
if match_re:
comment_nums = int(match_re.group(1))
else:
comment_nums = 0
# 正文
contment = response.css(".entry").extract()[0]
# # 标签
tag_list = response.css("p.entry-meta-hide-on-mobile a::text").extract()
tag_list = [element for element in tag_list if not element.strip().endswith("评论")]
tags = ",".join(tag_list)