爬虫—8小时入门版
# =============================================
# Attribution : Chengdu Test Department
# Time : 2023/3/17
# Author : Mikigo
# =============================================
具体解说 Requests 和 httpx 库的运用,并以爬取 deepin 论坛数据为例,解说爬虫结构 Scrapy 的运用办法。
一、Requests
1、简介
Requests 是 Python 最久负盛名的 HTTP 库,没有之一;K 神(Kenneth Reitz)的 for humans 系列中最有名的一个;
![爬虫—8小时入门版 爬虫—8小时入门版](https://www.6hu.cc/storage/2023/05/1683381431-37985e959848e7d.png)
有人甚至主张将 Requests 库合入 Python 规范库发布。只需你想做 HTTP 恳求,你肯定会想到 Requests。
Requests 特色:简略、简洁、高雅。
2、装置
体系环境:deepin
pip3 install requests
3、恳求
3.1、导入
import requests
一切的功用都在 requests 这个称号空间下。
3.2、GET 恳求
r = requests.get("https://www.baidu.com")
print(r.status_code)
print(r.text)
终端打印:
200
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equ ...... # 省掉
3.3、POST 恳求
https://httpbin.org
是 K 神的一个简略的 HTTP 服务,首要用于试用 requests 里边的一些功用,方便了解;
r = requests.post('https://httpbin.org/post', data={'key': 'value'})
print(r.status_code)
print(r.text)
终端打印:
200
{
"args": {},
"data": "",
"files": {},
"form": {
"key": "value"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Content-Length": "9",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.28.1",
"X-Amzn-Trace-Id": "Root=1-642aa9b9-259b189c1acb1e114a5d6bc7"
},
"json": null,
"origin": "110.191.179.216",
"url": "https://httpbin.org/post"
}
3.4、其他恳求
其他恳求办法不常用,如下:
r = requests.put('https://httpbin.org/put', data={'key': 'value'})
r = requests.delete('https://httpbin.org/delete')
r = requests.head('https://httpbin.org/get')
r = requests.options('https://httpbin.org/get')
3.5、恳求头(headers)
恳求头一般会加 UA(user agent),这个首要是模仿浏览器的行为,比方模仿运用 Firefox 浏览器:
headers = {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/109.0"
}
requests.get(url, headers=headers)
requests.post(url, headers=headers)
3.6、参数
(1) get 恳求参数
get 恳求的参数能够直接在url后边加参数,url?key1=value1&key2=value2
,即 url 后边加问号,然后紧接着多个参数的键和值,多个键值之间用 & 符号链接;
这种办法简略是简略,可是参数多了之后,url 会变得很长,看起来胀眼睛,为了更好的可读性,requests 支撑这样传递 get 恳求的参数:
params = {'key1': 'value1', 'key2': 'value2'}
r = requests.get('https://httpbin.org/get', params=params)
经过打印 r.url 你会发现,实践上也是给你转换成了前面那种 & 连接的办法;
(2)post 恳求参数
post 恳求参数一般是经过data参数传递,一般 data 是一个字典形式:
data = {'key1': 'value1', 'key2': 'value2'}
r = requests.post('https://httpbin.org/post', data=data)
print(r.text)
履行后终端输出:
{
"args": {},
"data": "",
"files": {},
"form": {
"key1": "value1",
"key2": "value2"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Content-Length": "23",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.28.1",
"X-Amzn-Trace-Id": "Root=1-642e7884-7f20428d523e7fda1da61f1a"
},
"json": null,
"origin": "110.191.179.216",
"url": "https://httpbin.org/post"
}
假如你拿到的参数,是一个 json 格局,能够直接传递给 json 参数:
jsons = '{"key1": "value1", "key2": "value2"}'
r = requests.post('https://httpbin.org/post', json=jsons)
print(r.text)
履行后终端输出:
{
"args": {},
"data": "\"{\\\"key1\\\": \\\"value1\\\", \\\"key2\\\": \\\"value2\\\"}\"",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Content-Length": "46",
"Content-Type": "application/json",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.28.1",
"X-Amzn-Trace-Id": "Root=1-642e79d4-1f0aeb1370ea94cf2c858f9d"
},
"json": "{\"key1\": \"value1\", \"key2\": \"value2\"}",
"origin": "110.191.179.216",
"url": "https://httpbin.org/post"
}
4、呼应
其实前面的比方现已有体现一点呼应了;
r = requests.post('https://httpbin.org/post', data={'key': 'value'})
r 为回来值的方针(Response),一般在项目中我一般用 rsp
来表明(后边的 rsp
和 r 是一个意思,都是表明回来值的方针);
rsp 既然是方针,那来看下方针的办法和属性,我们 Debug 跑一下就很清楚:
![爬虫—8小时入门版 爬虫—8小时入门版](https://www.6hu.cc/storage/2023/05/1683381508-245399fe3c64950.png)
接下来讲几个比较常用的属性和办法;
4.1、呼应内容
r.text
前面比方现已打印过,这儿就不打印了;
text
的解码是主动的,大多数情况下都能正常解码;
能够经过 encoding
来查看或修正编码办法:
print(r.encoding)
r.encoding = 'ISO-8859-1'
修正编码办法之后,再运用 r.text
就会以新的编码办法解码。
假如你发现回来的内容编码不对,你能够尝试修正不同的编码,这是个经验堆集的进程。
4.2、二进制呼应内容
非文本类恳求,一般回来的是二进制内容,此刻我们应该运用 content
办法:
r.content
将二进制文件保存下来,比方恳求回来一个 mp3 文件:
with open("my.mp3", "wb") as f:
f.write(r.content)
4.3、JSON呼应内容
一些 RESTful API 回来一般是 json 内容,我们能够直接运用:
r.json()
获取的类型为 Python 的字典类型;
假如呼应包括无效JSON,会抛 requests.exceptions.JSONDecodeError
反常。
5、高阶用法
5.1、Session
Session 方针能够在一次会话中能够有用的处理 cookie 耐久化的问题;
s = requests.Session()
# 设置一个cookie为123456789
s.get('https://httpbin.org/cookies/set/sessioncookie/123456789')
# 恳求一下
r = s.get('https://httpbin.org/cookies')
print(r.text)
履行后终端输出:
{
"cookies": {
"sessioncookie": "123456789"
}
}
5.2、Request
无论是前面讲到的 GET 、 POST 等恳求办法:
requests.get()
requests.post()
其底层都是经过调用 Request 这个类来完成的:
class Request():
def __init__(
self,
method=None,
url=None,
headers=None,
files=None,
data=None,
params=None,
auth=None,
cookies=None,
hooks=None,
json=None,
):
pass
因而我们当然能够直接跨过这一步,不让中间商赚差价,直接用 Request:
frome requests import Request
r = Request("GET", url, headers=headers)
r = Request("POST", url, headers=headers)
还没完,记得调用一下 prepare()
办法,然后运用 Session 里边的 send 办法:
举例:
from requests import Session, Request
s = Session()
r = Request("GET", 'https://httpbin.org/get')
prepped = r.prepare()
resp = s.send(prepped)
print(resp.text)
履行终端输出:
{
"args": {},
"headers": {
"Accept-Encoding": "identity",
"Host": "httpbin.org",
"User-Agent": "python-urllib3/1.26.13",
"X-Amzn-Trace-Id": "Root=1-642e84e2-1328a0210e2252741f20c648"
},
"origin": "110.191.179.216",
"url": "https://httpbin.org/get"
}
二、httpx
1、简介
虽然 Requests 根本现已能够处理大部分问题,但仍然有少部分问题无法处理,比方:HTTP/2(Requests 只支撑HTTP/1.1)、异步恳求等,这就需求用到 httpx;
httpx 号称下一代 HTTP 客户端,最开端是为了处理 Requests 不支撑异步恳求的问题,工程称号就叫:requests-async,后边全体迁移到 httpx 仓库中。
由于 httpx 从一开端便是根据 Requests 来搞的,所以它供给的接口几乎和 Requests 保持一致,这关于我们运用来说就简略多了。
2、装置
体系环境:deepin
pip3 install httpx
它还供给命令行东西:
pip3 install 'httpx[cli]'
我一般不咋习惯用命令行做接口恳求,所以根本都不装这玩意儿。
3、简略的比方
前面说了 httpx 和 Requests 供给的接口几乎一致,我们就用 Requests 教程里边的比方;
3.1、GET恳求
import httpx
r = httpx.get("https://www.baidu.com")
print(r.status_code)
print(r.text)
履行后终端输出:
200
<html>
<head>
<script>
location.replace(location.href.replace("https://","http://"));
</script>
</head>
<body>
<noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
</body>
</html>
3.2、POST恳求
import httpx
r = httpx.post('https://httpbin.org/post', data={'key': 'value'})
print(r.status_code)
print(r.text)
履行后终端输出:
200
{
"args": {},
"data": "",
"files": {},
"form": {
"key": "value"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Content-Length": "9",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "python-httpx/0.23.3",
"X-Amzn-Trace-Id": "Root=1-642f820c-019ccf8938faee564386038e"
},
"json": null,
"origin": "110.191.179.216",
"url": "https://httpbin.org/post"
}
你看,几乎是一毛相同。
行啦,都相同我们就不聊了,后边要点讲讲不相同的。
4、异步恳求
异步是一种并发办法,也便是一般说的“协程”,比多线程效率高许多;
httpx 的异步恳求首要依赖于规范库 asyncio,运用 async 和 await 关键词;
import asyncio
import httpx
async def my_get():
async with httpx.AsyncClient() as client:
r = await client.get("https://www.baidu.com")
print(r.text)
if __name__ == '__main__':
asyncio.run(my_get())
client 方针你能够了解为 Requests 里边的 Session 方针。
5、HTTP/2
老实讲 HTTP/2 的网站我还没机会爬过,所以我这儿还不太好找比方;
假如你在不小心遇到了也别慌,只需求加一个参数就好了;
import asyncio
import httpx
async def my_get():
async with httpx.AsyncClient(http2=True) as client:
r = await client.get("https://www.xxxxxx.com")
print(r.text)
if __name__ == '__main__':
asyncio.run(my_get())
在 httpx.AsyncClient()
里边,默许参数:
class AsyncClient:
def __init__(
self,
...
http1: bool = True,
http2: bool = False,
...
):
pass # 省掉其他源码
也便是说默许是开启的 http1,所以只需求在实例化 client 方针的时分,传入参数 http2=True
即可;
三、Scrapy
1、简介
Scrapy 是现阶段 Python 社区最盛行的爬虫结构,它能够极大的简化爬虫的编写难度,简化代码。
当然它不是 Python 社区仅有的爬虫结构,但我认为是现阶段最好用的爬虫结构。
经常用同学问,为啥要用 Scrapy,我用 requests 不能够吗?
我觉得这样解说:
- 不是一个类型
requests 最多算是爬虫东西,不同的人写出来的爬虫代码都不同,重复代码还多,并且关于一些高档的使用场景,如:多线程处理、异步处理、耐久化等,估量没几个人能处理的很完美,最终爬下来的数据还要找一堆东西来解析处理,比方:re、BeautifulSoup、lxml等,属实让人挠头;
而爬虫结构一般供给了简略的装备,运用很少的代码就能完成杂乱的功用,代码量少了,并且底层也为你处理了很对问题,结构在解析数据也有自带的计划,所以你只需求依照结构所界说好的规范,就能够轻松完成各种使命;
- 不是一个圈子
Scrapy 首要用于数据爬取,所以说它是爬虫结构,你说用它来做一些 POST 恳求发个数据啥的,我们貌似没这么用过;
而 requests 只需是网络接口恳求都能用,爬数据也能够,但你要说爬数据有多强呢,就要看运用的人有多强了;
总结:
- 新手、老司机做小使命,用哪个都无所谓,用结构的话会轻松许多;
- 新手做大使命,用结构,不要想,省时省力;
- 老司机做大使命,用东西能够做,便是有点费事;用结构也能搞,可是不能秀出你的实力;
2、装置
体系环境:deepin
pip3 install Scrapy
3、创立项目
我们就爬取 deepin 论坛的贴子,找找感觉;
创立一个爬虫项目名为:deepin_bbs_spider
cd ~
scrapy startproject deepin_bbs_spider
工程目录结构:
deepin_bbs_spider
├── deepin_bbs_spider
│ ├── __init__.py
│ ├── items.py # 数据类型界说
│ ├── middlewares.py # 中间件
│ ├── pipelines.py # 数据管道
│ ├── settings.py # 装备项
│ └── spiders # 放爬虫脚本的目录
│ └── __init__.py
└── scrapy.cfg # 部署装备文件
4、开端写爬虫
在 ~/deepin_bbs_spider/deepin_bbs_spider/spiders
目录下写我们的爬虫脚本文件,创立一个爬虫,方针是爬取论坛里边帖子内容:
# bbs_spider.py
import scrapy
class BbsSpiderSpider(scrapy.Spider):
name = "bbs_spider"
allowed_domains = ["bbs.deepin.org"]
start_urls = ["https://bbs.deepin.org/?offset=0&limit=20&order=updated_at&where=&languages=zh_CN#comment_title"]
def parse(self, response):
post_items = response.css("app-main-pc > div > div:nth-child(3) > app-post-pc")
for post_item in post_items:
url = post_item.css("a.post_lin_pc::attr(href)").get()
title = post_item.css("span.ng-star-inserted::text").getall()
print("url:", url)
print("title", title)
啥也不说,先跑起来试试:
cd ~/deepin_bbs_spider
scrapy crawl bbs_spider
跑完之后,终端就会有输出爬取到的帖子信息:
![爬虫—8小时入门版 爬虫—8小时入门版](https://www.6hu.cc/storage/2023/05/1683381514-91cd7a87b83b86f.png)
你先别管其他的,至少我们能爬到数据了,接下来我们渐渐介绍上面这些代码是怎样来的~;
5、逻辑解说
5.1、生成爬虫模板
看了上面的示例,有同学肯定要问,你咋知道要写个类呢,你咋知道要写个 parse
函数呢?
我确实不知道,scrapy
也知道我们不知道,所以做了个东西主动生成:
scrapy genspider <spider name> <spider url>
用子命令 genspider
,后边加爬虫的称号(spider name),再加要爬取地址(url),就能够在 spiders
目录下主动生成一个 py
文件;
比方,我们像这样:
scrapy genspider bbs_spider "https://bbs.deepin.org"
履行之后就会主动生成 py
文件:
import scrapy
class BbsSpiderSpider(scrapy.Spider):
name = "bbs_spider"
allowed_domains = ["bbs.deepin.org"]
start_urls = ["https://bbs.deepin.org"]
def parse(self, response):
pass
简略解说一下:
- 爬虫类是要承继
scrapy.Spider
的,这个不要去动,知道承继就对了; - 类变量
name
是爬虫的称号,这玩意儿便是个代号,你想改成王大锤都行,一般赖得去管; - 类变量
allowed_domains
爬虫域名; - 类变量
start_urls
爬虫方针地址,能够给多个; - 实例办法
parse(self, response)
也是固定写法,函数称号最好不动,参数称号不能改,由于是 scrapy 回来的一个 Selector 方针;
这儿边中心逻辑便是在 parse
函数里边去写,你能够了解成 response
便是回来的页面信息,你只需求在这儿边去提取想要的数据就好了;
response
供给一些办法,能够很方便的进行页面信息提取;
5.2、爬虫编写办法
前面提到爬虫脚本里边 response
,它是我们编写代码的中心,一切的数据提取都从这儿来;
下面我们讲讲数据的提取办法,这儿多嘴一句,我默许大家都是有一点前端根底的,否则下面部分内容可能需求去学习下 html、css相关常识;
首要来讲 css 提取办法,css 的解析是十分灵敏的,先用 F12 看下 html 源码:
![爬虫—8小时入门版 爬虫—8小时入门版](https://www.6hu.cc/storage/2023/05/1683381521-f065c303eea6490.png)
能够看到一切的帖子都在 app-post-pc
标签下面,我们能够这样写:
def parse(self, response):
post_items = response.css("app-post-pc")
假如你是运用右键仿制的选择器,可能是一个很长的表达式,不太高雅也不利于维护,我个人不太主张运用直接仿制表达式,而应该经过调查自己写;
这样的话,post_items
就获取到了一切帖子的 app-post-pc
标签,再看下 app-post-pc
标签下都有啥:
![爬虫—8小时入门版 爬虫—8小时入门版](https://www.6hu.cc/storage/2023/05/1683381527-696e32407252c5e.png)
能够看到在 app-post-pc
标签下还有 a 标签,保存了帖子的概况地址(post_url
),然后在 a 标签下的 span 标签保存了帖子的类型和标题(title
),因而我们想办法把这两个拿到:
def parse(self, response):
post_items = response.css("app-post-pc")
for post_item in post_items:
url = post_item.css("a.post_lin_pc::attr(href)").get()
title = post_item.css("span.ng-star-inserted::text").getall()
print("url:", url)
print("title", title)
先用 for 循环把 post_items
里边每个 Selector
方针里边的 url
和 title
拿到;
post_item
便是单个的 Selector
方针,我们在它的根底上再经过 css 办法获取到我们想要的数据;(也能够运用 Xpath 技术获取)
-
url
是在 a 标签里边的 href 属性里边,因而:
post_item.css("a.post_lin_pc::attr(href)").get()
表达式里边的 ::attr(href)
这部分是 Scrapy 特有的,::
表明取值,attr(href)
表明经过 href
属性取值;
get()
办法表明取第一个值,getall()
办法表明取一切的值;(也兼容老版别的 extract_first()
和 extract()
办法,意思是对应相同的,不过明显get()
这种可读性更好更易于了解。)
-
title
在 span 标签里边:
post_item.css("span.ng-star-inserted::text").getall()
text
也是 Scrapy 特有的,表明把标签的文本取出来;
十分好了解对吧,只需你略微有点前端常识,就能够轻松把表达式写出来;
5.3、获取数据
前面比方是将获取到的数据打印出来,实践事务里边我们肯定是需求将数据保存下来的;
首要我们在 items.py
里边界说数据类型:
# items.py
import scrapy
class DeepinBbsSpiderItem(scrapy.Item):
# define the fields for your item here like:
url = scrapy.Field()
title = scrapy.Field()
写法十分简略,一致运用 scrapy.Field()
来界说就行了;
然后,回到爬虫脚本里边:
import scrapy
from deepin_bbs_spider.items import DeepinBbsSpiderItem
class BbsSpiderSpider(scrapy.Spider):
... # 省掉部分代码
def parse(self, response):
item = DeepinBbsSpiderItem()
post_items = response.css("app-post-pc")
for post_item in post_items:
item["url"] = post_item.css("a.post_lin_pc::attr(href)").get()
item["title"] = post_item.css("span.ng-star-inserted::text").getall()
yield item
将 items.py
里边的 DeepinBbsSpiderItem
导进来,实例化一个方针,然后将获取到的数据仿制给这个方针,运用 item["url"]
这种给字典添加的办法,留意要和 items.py
里边界说的字段称号保持一致;
最终,运用 yield
将数据回来出来就行了;
将数据写入到 csv 文件里边:
scrapy crawl bbs_spider -o bbs.csv
-o
表明导出数据,履行后,查看 bbs.csv 文件:
![爬虫—8小时入门版 爬虫—8小时入门版](https://www.6hu.cc/storage/2023/05/1683381533-ed5ef98214f86f5.png)
这样就将爬取到的数据保存到了一个 csv 文件;
5.4、处理数据
在爬虫脚本里边获取到原始数据之后,我们还有可能会拿数据做进一步处理,比方还想写入数据库、写入 Excel等等;
这些进一步的操作,我们一般是在数据管道 pipelines.py
里边来处理:
class DeepinBbsSpiderPipeline:
def process_item(self, item, spider):
return item
这儿的 item
便是每一条数据;
比方,你想写入 MySQL
数据库(首要要保证数据库表、字段等正常):
import pymysql
class DeepinBbsSpiderPipeline:
def __init__(self):
# 在结构函数里边创立数据库连接和游标
def open_spider(self, spider):
# open_spider 是这个管道开端时要履行的;这儿能够不要
def close_spider(self, spider):
# close_spider 写入封闭数据库的代码
def process_item(self, item, spider):
# 在这儿做写入数据库的动作
return item
在上面注释里边写了写入数据库的编写逻辑,由于我们首要想解说数据管道的操作逻辑,数据库的代码数据根本操作,就不做具体代码示例了,往上搜 pymysql
的运用许多,依照注释的逻辑,对号入座就行了;
假如想写入 Excel 表格逻辑是相同的,也能够表格和数据库一起写入,在 pipelines.py
里边再界说一个管道类就行了;
留意,数据管道逻辑写完之后,要在 settings.py
里边修正装备:
ITEM_PIPELINES = {
"deepin_bbs_spider.pipelines.DeepinBbsSpiderPipeline": 300,
# "deepin_bbs_spider.pipelines.XxxxPipeline": 2,
}
ITEM_PIPELINES
是一个字典,key 是数据管道,value 是一个数字;
value 首要用于多个管道排序的,由于在 pipelines.py
里边能够界说多个数据管道类,它们履行的先后顺序由 value 来控制,数字越小越先履行;
假如你就一个数据管道类,这个 value 给多少都无所谓;
另外提示,在 process_item()
最终一定要 return item
,否则存在多个数据管道的时分,后履行的数据管道就拿不到数据了;
好,装备完之后,就能够再次履行了;
5.5、从基层页面解析数据
这部分内容相对来讲是难点,搞懂了这部分,就几乎能处理对大部分数据爬取了;
来,开端燥起来~~
前面我们获取到了帖子的 url
和 title
,有同学可能要问了,这个帖子的正文内容哪里;
正文内容在帖子的 url
里边,假如我们要一起获取帖子的正文内容,就需求做以下处理;
![爬虫—8小时入门版 爬虫—8小时入门版](https://www.6hu.cc/storage/2023/05/1683381538-72a9a67ead6a6b3.png)
5.5.1、回调逻辑
首要,前面获取的 url
不是一个完好的链接,我们需求略微处理以下:
class BbsSpiderSpider(scrapy.Spider):
base_url = "https://bbs.deepin.org"
def parse(self, response):
item = DeepinBbsSpiderItem()
post_items = response.css("app-post-pc")
for post_item in post_items:
item["url"] = post_item.css("a.post_lin_pc::attr(href)").get().replace("/en", self.base_url)
# 省掉部分代码
我们前面获取的 url
是这样的: /en/post/254787
,因而做一个替换处理;
然后,我们拿着这个 url
持续做恳求:
class BbsSpiderSpider(scrapy.Spider):
base_url = "https://bbs.deepin.org"
def parse(self, response):
item = DeepinBbsSpiderItem()
post_items = response.css("app-post-pc")
for post_item in post_items:
item["url"] = post_item.css("a.post_lin_pc::attr(href)").get().replace("/en", self.base_url)
yield scrapy.Request(
url=item["url"],
callback=self.post_parse,
cb_kwargs={"item": item}
)
def post_parse(self, response, **kwargs):
item = kwargs.get("item")
这儿需求用 yield
回来并结构 scrapy.Request 方针,传入三个参数:
-
url 便是基层页面的地址;
-
callback 传入回调函数方针,由于
parse()
这个函数是处理当前页面的逻辑,基层页面就不能在这个函数里边持续处理了,而是要新写一个函数来处理;写法和
parse()
相似,函数名能够自己定, 参数仍然是 response 方针;留意,参数传入是
callback=self.post_parse
,后边没有加括号哈,由于我们不是在这儿调用函数,是传入函数方针,也便是只需函数名; -
cb_kwargs 是为了给
post_parse()
函数传递 item 参数,是一个字典类型,这样在post_parse(self, response, **kwargs)
里边的kwargs
就能拿到 item 的值,我们后续拿到正文之后,持续组装到 item 里边就行了;
5.5.2、基层页面解析
基层页面的解析,逻辑和前面相同,先看下 html 源码:
![爬虫—8小时入门版 爬虫—8小时入门版](https://www.6hu.cc/storage/2023/05/1683381544-685dee98d8822a2.png)
获取正文:
def post_parse(self, response, **kwargs):
item = kwargs.get("item")
post_info = response.css("div.post_conten > div.post_edit.ng-star-inserted > div > div > p::text").getall()
post_info
获取的成果为:
['1、体系盘分配了40g,这才一个月就快满了,怎样调大点,后边还有100G空间。', '2、使用商铺啥时分放出conky?']
做一个字符串组装:
post_info = "".join(response.css("div.post_conten > div.post_edit.ng-star-inserted > div > div > p::text").getall())
这样的话,我们就获取到了正文的数据,添加到 item 方针中:
def post_parse(self, response, **kwargs):
item = kwargs.get("item")
post_info = "".join(response.css("div.post_conten > div.post_edit.ng-star-inserted > div > div > p::text").getall())
item["post_info"] = post_info
yield item
留意,在 items.py
中把新的字段也添加上:
# items.py
class DeepinBbsSpiderItem(scrapy.Item):
...
post_info = scrapy.Field()
最终,跑一下爬虫;
5.5.3、多层数据传递问题
到目前方位,完好的爬虫脚本:
import scrapy
from deepin_bbs_spider.items import DeepinBbsSpiderItem
class BbsSpiderSpider(scrapy.Spider):
name = "bbs_spider"
allowed_domains = ["bbs.deepin.org"]
start_urls = ["https://bbs.deepin.org/?offset=0&limit=20&order=updated_at&where=&languages=zh_CN#comment_title"]
base_url = "https://bbs.deepin.org"
def parse(self, response):
item = DeepinBbsSpiderItem()
post_items = response.css("app-main-pc > div > div:nth-child(3) > app-post-pc")
for post_item in post_items:
item["url"] = post_item.css("a.post_lin_pc::attr(href)").get().replace("/en", self.base_url)
item["title"] = "".join(post_item.css("span.ng-star-inserted::text").getall()[:2])
yield scrapy.Request(
url=item["url"],
callback=self.post_parse,
cb_kwargs={"item": item}
)
def post_parse(self, response, **kwargs):
item = kwargs.get("item")
post_info = "".join(response.css("div.post_conten > div.post_edit.ng-star-inserted > div > div > p::text").getall())
item["post_info"] = post_info
yield item
运用命令跑一下爬虫:
scrapy crawl bbs_spider -o bbs.csv
你会惊讶的发现,怎样一切的 title 和 url 数据相同,开端置疑自己逻辑是不是写错了;
其实,我们代码逻辑是没问题的,只不过在多层数据传递的进程中,需求特别处理下,处理办法很简略:
- 导入
from copy import deepcopy
模块,将cb_kwargs={'item': item}
更改为cb_kwargs={'item': deepcopy(item)
; - 最终一行代码
yield item
修正成yield deepcopy(item)
就彻底ok
了;
改完之后再跑一下,几乎完美。
5.5.4、多页面爬取
到现在我们怕去了第一页的数据,那还想爬取后边的页怎样办?
有同学说,好办,start_urls
不是一个列表吗,把多个 url 放进去不就完了;
不得不说,这样是能够的,便是不够高雅。
经过仔细调查,我们能够发现一些规则:
![爬虫—8小时入门版 爬虫—8小时入门版](https://www.6hu.cc/storage/2023/05/1683381549-0a49b0e6bb86cc9.png)
在地址中只有 offset
参数在改变,第一页是 0,第二页是 1,十分有规则,因而我们能够动态生成:
class BbsSpiderSpider(scrapy.Spider):
# start_urls = ["https://bbs.deepin.org/?offset=0&limit=20&order=updated_at&where=&languages=zh_CN#comment_title"]
def start_requests(self):
for i in range(5):
yield scrapy.Request(url=f"https://bbs.deepin.org/?offset={i}&limit=20&order=updated_at&where=&languages=zh_CN#comment_title")
运用 start_requests()
函数替代 start_urls
;
在里边写个 for 循环,要爬取多少页填入 range
函数就行了,动态生成多个 scrapy.Request
方针,留意要用 yield 哦~~
5.5.5、完好的示例
爬虫脚本 bbs_spider.py
:
from copy import deepcopy
import scrapy
from deepin_bbs_spider.items import DeepinBbsSpiderItem
class BbsSpiderSpider(scrapy.Spider):
name = "bbs_spider"
allowed_domains = ["bbs.deepin.org"]
base_url = "https://bbs.deepin.org"
def start_requests(self):
for i in range(5):
yield scrapy.Request(url=f"https://bbs.deepin.org/?offset={i}&limit=20&order=updated_at&where=&languages=zh_CN#comment_title")
def parse(self, response):
item = DeepinBbsSpiderItem()
post_items = response.css("app-main-pc > div > div:nth-child(3) > app-post-pc")
for post_item in post_items:
item["url"] = post_item.css("a.post_lin_pc::attr(href)").get().replace("/en", self.base_url)
item["title"] = "".join(post_item.css("span.ng-star-inserted::text").getall()[:2])
yield scrapy.Request(
url=item["url"],
callback=self.post_parse,
cb_kwargs={"item": deepcopy(item)}
)
def post_parse(self, response, **kwargs):
item = kwargs["item"]
post_info = "".join(
response.css("div.post_conten > div.post_edit.ng-star-inserted > div > div > p::text").getall()
)
item["post_info"] = post_info
yield deepcopy(item)
数据类型 items.py
:
import scrapy
class DeepinBbsSpiderItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
url = scrapy.Field()
title = scrapy.Field()
post_info = scrapy.Field()
装备 settings.py
:(省掉了没有启用的装备项)
BOT_NAME = "deepin_bbs_spider"
SPIDER_MODULES = ["deepin_bbs_spider.spiders"]
NEWSPIDER_MODULE = "deepin_bbs_spider.spiders"
# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
# Obey robots.txt rules
ROBOTSTXT_OBEY = True
# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en",
}
# Set settings whose default value is deprecated to a future-proof value
REQUEST_FINGERPRINTER_IMPLEMENTATION = "2.7"
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
FEED_EXPORT_ENCODING = "utf-8"
假如你自己玩起来有点小问题,能够尝试参阅我的代码:github.com/mikigo/deep…
6、调试
6.1、数据获取调试
在运用 response.css()
表达式时,一般我们需求进行调试,看表达式写得对不对,当然你能够经过履行爬虫然后打印数据,可是这种办法有点费事;
Scrapy 供给了一个快捷的调试办法,在终端输入:
scrapy shell <scrapy url>
<scrapy url>
是你要爬取的地址,比方前面我们想获取帖子正文的内容,能够这样调试:
scrapy shell https://bbs.deepin.org/post/254892
进入终端交互式,输入:
>>> response.css("div.post_conten > div.post_edit.ng-star-inserted > div > div > p::text").getall()
['1、体系盘分配了40g,这才一个月就快满了,怎样调大点,后边还有100G空间。', '2、使用商铺啥时分放出conky?']
能够看到回来的成果,假如回来为空,就说明表达式可能有点问题;
6.2、Pycharm Debug
Scrapy 由于封装得比较好,发动爬虫是经过命令行发动,可是这有个问题,便是不支撑在编辑器里边 Debug 运行,导致你调试代码进程中可能会不断的在终端发动爬虫,有点费力;
经过一番折腾,总算道破了天机~
(1)先在工程下随便找一个 py
文件,里边啥也不写,履行一下,然后点这儿:
![爬虫—8小时入门版 爬虫—8小时入门版](https://www.6hu.cc/storage/2023/05/1683381556-6d53ec3a51641f8.png)
(2)在体系中找到 scrapy 包中的 cmdline.py 文件,这个你得略微知道点 Python 包管理的一些常识,比方我的在这儿:
/home/mikigo/.local/lib/python3.7/site-packages/scrapy/cmdline.py
(4)在 Name
里边写个你喜爱的姓名,比方我写:Scrapy
(4)在 Script path
里边把 cmdline.py
的途径填进去;
(5)在 Parameters
里边填入 Scrapy 的参数:crawl bbs_spider -o bbs.csv
;
![爬虫—8小时入门版 爬虫—8小时入门版](https://www.6hu.cc/storage/2023/05/1683381561-ed8e3d01cfee81b.png)
(6)点击右下角的 【ok】,在主界面点【Debug】就能够进行调试了,妙啊~~
![爬虫—8小时入门版 爬虫—8小时入门版](https://www.6hu.cc/storage/2023/05/1683381566-ec233341afe25d2.png)
7、完毕语
到这儿 Scrapy 的根底入门就完毕了,一般的小网站能够轻松快速的搞定,几乎 yyds~~
从完好示例我们不难看出,爬虫脚本简略的 30 来行代码加上简略的装备,就能够爬取大量的数据,并且速度十分快,比照你用 requests 去裸写看看,你会发现差距不是一般的大;
关于其他的一些细节还能够完善,比方:署理、异步、中间件、与其他主动化东西扩展(Selenium),后续精力再补充~~