Scrapy+selenium爬取简书全站

环境

  • Ubuntu 18.04

  • Python 3.8

  • Scrapy 2.1

爬取内容

  • 文字标题
  • 作者
  • 作者头像
  • 发布日期
  • 内容
  • 文章连接
  • 文章ID

思路

  • 分析简书文章的url规则
  • 使用selenium请求页面
  • 使用xpath获取需要的数据
  • 异步存储数据到MySQL(提高存储效率)

实现

前戏:
  • 创建scrapy项目
  • 建立crawlsipder爬虫文件
  • 打开pipelinesmiddleware
第一步:分析简书文章的url

可以看到url规则为jianshu.com/p/文章ID,然后再crawlsipder中设置url规则

class JsSpider(CrawlSpider):
name = 'js'
allowed_domains = ['jianshu.com']
start_urls = ['http://jianshu.com/']
rules = (
Rule(LinkExtractor(allow=r'.+/p/[0-9a-z]{12}.*'), callback='parse_detail', follow=True),
)
第二步:使用selenium请求页面

设置下载器中间件

  • 由于作者、发布日期等数据由Ajax加载,所以使用selenium来获取页面源码以方便xpath解析

  • 有时候请求会卡在一个页面,一直未加载完成,所以需要设置超时时间

  • 同理Ajax也可能未加载完成,所以需要显示等待加载完成

from selenium import webdriver
from scrapy.http.response.html import HtmlResponse
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.by import By class SeleniumDownloadMiddleware(object):
def __init__(self):
self.driver = webdriver.Chrome() def process_request(self, request, spider):
while True:
# 超时重新请求
try:
self.driver.set_page_load_timeout(1)
self.driver.get(request.url)
except:
pass
finally:
try:
# 等待ajax加载,超时了就重来
WebDriverWait(self.driver, 1).until(
expected_conditions((By.CLASS_NAME, 'rEsl9f'))
)
except:
continue
finally:
break
url = self.driver.current_url
source = self.driver.page_source
response = HtmlResponse(url=url, body=source, request=request, encoding='utf-8')
return response

注意提前将 chromedriver 放到/user/bin下,或者自行指定执行路径。windows下可以讲其添加到环境变量下。

第三步:使用xpath获取需要的数据

设置好item

import scrapy

class JianshuCrawlItem(scrapy.Item):
title = scrapy.Field()
content = scrapy.Field()
author = scrapy.Field()
avatar = scrapy.Field()
pub_time = scrapy.Field()
origin_url = scrapy.Field()
article_id = scrapy.Field()

分析所需数据的xpath路径,进行获取需要的数据,并交给pipelines处理

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..items import JianshuCrawlItem as Jitem class JsSpider(CrawlSpider):
name = 'js'
allowed_domains = ['jianshu.com']
start_urls = ['http://jianshu.com/']
rules = (
Rule(LinkExtractor(allow=r'.+/p/[0-9a-z]{12}.*'), callback='parse_detail', follow=True),
) def parse_detail(self, response):
# 使用xpath获取数据
title = response.xpath("//h1[@class='_2zeTMs']/text()").get()
author = response.xpath("//a[@class='_1OhGeD']/text()").get()
avatar = response.xpath("//img[@class='_13D2Eh']/@src").get()
pub_time = response.xpath("//div[@class='s-dsoj']/time/text()").get()
content = response.xpath("//article[@class='_2rhmJa']").get()
origin_url = response.url
article_id = origin_url.split("?")[0].split("/")[-1]
print(title) # 提示爬取的文章
item = Jitem(
title=title,
author=author,
avatar=avatar,
pub_time=pub_time,
origin_url=origin_url,
article_id=article_id,
content=content,
)
yield item
第四步:存储数据到数据库中

我这里用的数据库是MySQL,其他数据同理,操作数据的包是pymysql

提交数据有两种思路,顺序存储和异步存储

由于scrapy是异步爬取,所以顺序存储效率就会显得比较慢,推荐采用异步存储

顺序存储:实现简单、效率低

class JianshuCrawlPipeline(object):
def __init__(self):
dbparams = {
'host': '127.0.0.1',
'port': 3306,
'user': 'debian-sys-maint',
'password': 'lD3wteQ2BEPs5i2u',
'database': 'jianshu',
'charset': 'utf8mb4',
}
self.conn = pymysql.connect(**dbparams)
self.cursor = self.conn.cursor()
self._sql = None def process_item(self, item, spider):
self.cursor.execute(self.sql, (item['title'], item['content'], item['author'],
item['avatar'], item['pub_time'],
item['origin_url'], item['article_id']))
self.conn.commit()
return item @property
def sql(self):
if not self._sql:
self._sql = '''
insert into article(id,title,content,author,avatar,pub_time,origin_url,article_id)\
values(null,%s,%s,%s,%s,%s,%s,%s)'''
return self._sql

异步存储:复杂、效率高

import pymysql
from twisted.enterprise import adbapi class JinshuAsyncPipeline(object):
'''
异步储存爬取的数据
''' def __init__(self):
# 连接本地mysql
dbparams = {
'host': '127.0.0.1',
'port': 3306,
'user': 'debian-sys-maint',
'password': 'lD3wteQ2BEPs5i2u',
'database': 'jianshu',
'charset': 'utf8mb4',
'cursorclass': pymysql.cursors.DictCursor
}
self.dbpool = adbapi.ConnectionPool('pymysql', **dbparams)
self._sql = None @property
def sql(self):
# 初始化sql语句
if not self._sql:
self._sql = '''
insert into article(id,title,content,author,avatar,pub_time,origin_url,article_id)\
values(null,%s,%s,%s,%s,%s,%s,%s)'''
return self._sql def process_item(self, item, spider):
defer = self.dbpool.runInteraction(self.insert_item, item) # 提交数据
defer.addErrback(self.handle_error, item, spider) # 错误处理 def insert_item(self, cursor, item):
# 执行SQL语句
cursor.execute(self.sql, (item['title'], item['content'], item['author'],
item['avatar'],
item['pub_time'],
item['origin_url'], item['article_id'])) def handle_error(self, item, error, spider):
print('Error!')

总结

  • 类似简书这种采用Ajax技术的网站可以使用selenium轻松爬取,不过效率相对解析接口的方式要低很多,但实现简单,如果所需数据量不大没必要费劲去分析接口。
  • selenium方式访问页面时,会经常出现加载卡顿的情况,使用超时设置和显示等待避免浪费时间

Github:https://github.com/aduner/jianshu-crawl

博客地址:https://www.cnblogs.com/aduner/p/12852616.html

Scrapy+selenium爬取简书全站的更多相关文章

  1. Node爬取简书首页文章

    Node爬取简书首页文章 博主刚学node,打算写个爬虫练练手,这次的爬虫目标是简书的首页文章 流程分析 使用superagent发送http请求到服务端,获取HTML文本 用cheerio解析获得的 ...

  2. python3 爬取简书30日热门,同时存储到txt与mongodb中

    初学python,记录学习过程. 新上榜,七日热门等同理. 此次主要为了学习python中对mongodb的操作,顺便巩固requests与BeautifulSoup. 点击,得到URL https: ...

  3. Python爬取简书主页信息

    主要学习如何通过抓包工具分析简书的Ajax加载,有时间再写一个Multithread proxy spider提升效率. 1. 关键点: 使用单线程爬取,未登录,爬取简书主页Ajax加载的内容.主要有 ...

  4. python2.7 爬取简书30日热门专题文章之简单分析_20170207

    昨天在简书上写了用Scrapy抓取简书30日热门文章,对scrapy是刚接触,跨页面抓取以及在pipelines里调用settings,连接mysql等还不是很熟悉,今天依旧以单独的py文件区去抓取数 ...

  5. 【python3】爬取简书评论生成词云

    一.起因: 昨天在简书上看到这么一篇文章<中国的父母,大都有毛病>,看完之后个人是比较认同作者的观点. 不过,翻了下评论,发现评论区争议颇大,基本两极化.好奇,想看看整体的评论是个什么样, ...

  6. scrapy爬取简书整站文章

    在这里我们使用CrawlSpider爬虫模板, 通过其过滤规则进行抓取, 并将抓取后的结果存入mysql中,下面直接上代码: jianshu_spider.py # -*- coding: utf-8 ...

  7. 使用scrapy+selenium爬取淘宝网

    --***2019-3-27测试有效***---- 第一步: 打开cmd,输入scrapy startproject taobao_s新建一个项目. 接着cd 进入我们的项目文件夹内输入scrapy ...

  8. 爬虫第六篇:scrapy框架爬取某书网整站爬虫爬取

    新建项目 # 新建项目$ scrapy startproject jianshu# 进入到文件夹 $ cd jainshu# 新建spider文件 $ scrapy genspider -t craw ...

  9. 爬取简书图片(使用BeautifulSoup)

    import requests from bs4 import BeautifulSoup url_list = [] kv = {'User-Agent':'Mozilla/5.0'} r = re ...

随机推荐

  1. 数据结构和算法(Golang实现)(14)常见数据结构-栈和队列

    栈和队列 一.栈 Stack 和队列 Queue 我们日常生活中,都需要将物品排列,或者安排事情的先后顺序.更通俗地讲,我们买东西时,人太多的情况下,我们要排队,排队也有先后顺序,有些人早了点来,排完 ...

  2. FJUT2019暑假第二次周赛题解

    A 服务器维护 题目大意: 给出时间段[S,E],这段时间需要人维护服务器,给出n个小时间段[ai,bi],代表每个人会维护的时间段,每个人维护这段时间有一个花费,现在问题就是维护服务器[S,E]这段 ...

  3. shell脚本知识

    1.提示符变量PS1 修改提示符变量:PS1="[u\@\h \t \w]" 修改环境变量设置文件bash_profile需要使用source或者.加上该文件使之生效 位置参数从1 ...

  4. 基于 Njmon + InfluxDB + Grafana 实现性能指标实时可视监控

    引言 最近逛 nmon 官网时,发现了一个新工具 njmon,功能与 nmon 类似,但输出为 JSON 格式,可以用于服务器性能统计. 可以使用 njmon 来向 InfluxDB 存储服务器性能统 ...

  5. tensorflow2.x 报错 Could not load dynamic library 'cudart64_101.dll'

    当我们使用 tensorflow 最新版本的时候 ,会出现这样的错误 -- ::] Could not load dynamic library 'cudart64_101.dll'; dlerror ...

  6. Salesforce学习 | 系统管理员Admin如何添加用户

    作为世界排名第一的CRM云计算软件,不管的是500强还是中小企业,越来越多的公司都选择使用Salesforce来分享客户信息,管理和开发具有更高收益的客户关系.Salesforce Administr ...

  7. Laravel joinSub 子查询的写法

    $subQuery = $model::query() ->from('table1 as a') ->getQuery(); $query = $model::query() -> ...

  8. MergeSort归并排序和利用归并排序计算出数组中的逆序对

    首先先上LeetCode今天的每日一题(面试题51. 数组中的逆序对): 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数. ...

  9. Laravel - 上手实现 - 邮件发送

    Laravel 自带 SwiftMailer 库,集成了多种邮件API,可以很方便的实现邮件的发送. 我们使用到的是SMTP(Simple Message Transfer Protocol)简单邮件 ...

  10. 递归复制&查看文件夹下的指定后缀的文件

    <?php header("content-type:text/html;charset=utf8"); set_time_limit(0); $dir = "d: ...