目录
一: 建立数据模型:
- 1:定义数据模型的作用:提前规划好要爬取那些数据。
- 2:定义数据模型的位置:items.py
1:在items.py文件中自定义要提取的字段:
import scrapy
class MyspiderItem(scrapy.Item):
name = scrapy.Field()
level = scrapy.Field()
text = scrapy.Field()
2:在itcast.py中只能使用这三个定义的,别的就会报错。
class ItcastSpider(scrapy.Spider):
...
def parse(self, response):
"""解析url地址返回的响应对象"""
# 响应对象,可以直接执行xpath语法,提取二进制文本数据: response.body
li_list = response.xpath("//div[@class='tea_con']//ul//li")
for li in li_list:
item = {}
item['name'] = li.xpath(".//h3/text()").extract_first()
item['level'] = li.xpath(".//h4/text()").extract_first()
item['text'] = li.xpath(".//p/text()").extract_first()
# 将数据交付给引擎,再由引擎传递到数据管道中。
yield item
二:Scrapy如何实现翻页请求:
需求:爬取千千音乐的43页歌单列表的标题:
1:前端网页分析
1: url地址分析:
https://music.taihe.com/songlist
https://music.taihe.com/songlist?subCateId=&pageNo=2
https://music.taihe.com/songlist?subCateId=&pageNo=43
说明:地址是由于pageNo来决定的。
2: 分析,首先我要获取最顶层的div标签的Xpath语法:
//[@id="__layout"]/div/div[2]/div[3]/div
//[@id="__layout"]/div/div[2]/div[3]/div[16]/div[2]
2:编写代码:
1:创建爬虫:
2:代码编写:
items.py中创建模板:
class QianQianItem(scrapy.Item):
title = scrapy.Field()
qianqian_music.py:
import scrapy
from myspider.items import QianQianItem
def all_url():
for i in range(1, 44):
url = 'https://music.taihe.com/songlist' + "?subCateId=&pageNo={}".format(i + 1)
yield url, i + 1
class QianqianMusicSpider(scrapy.Spider):
# 爬虫的名字
name = 'qianqian_music'
# 爬虫允许爬取的域范围
allowed_domains = ['https://music.taihe.com/']
# 起始的url
start_urls = ['https://music.taihe.com/songlist']
# 创建生成url的生成器
make_url = all_url()
def parse(self, response):
# 1: 先分组,再分组提取数据:
tr_list = response.xpath('//div[@class = "tracklist-item fl"]')
# 2: 提取每一页的数据:
for tr in tr_list:
item = QianQianItem()
item['title'] = tr.xpath("./div[2]/text()").extract_first()
print(item)
# 3: 构建下一页url请求:
# https: // music.taihe.com / songlist?subCateId = & pageNo = 2
url, i = next(self.make_url)
print(url)
if i != 45:
print("第{}页。。。。。。。。。。。。".format(i))
next_request = scrapy.Request(url=url, callback=self.parse, dont_filter=True)
yield next_request
3: 代码出现的问题:
错误一:
Scrapy框架底层会将重复的url请求进行去重,我们的url由于不是完全一致的,但是Scrapy发现我们url都是差不多的,所以除了第一个全部给去掉了。
解决方案:在scrapy.Request(url=url, callback=self.parse, dont_filter=True)中加入:dont_filter=True。
或者:allowed_domains = [‘https://music.taihe.com/’]中加入这个url。
错误二:创建生成器的要让他变成一个类属性,这样每次next()函数会调用一次。如果直接 url, i = next(all_url()),导致每次执行都是第一次执行yield关键字停止的状态。执行结果就是死循环爬取网站第二页。
4: 运行测试:
三:scrapy.Request参数:
- 1: url :请求的url。
- 2:callback : 当前的url应该交给哪个响应处理。
- 3:meta:不同解析函数之间传递。
- 4:dont_filter: 默认为False,会过滤请求的url地址,即请求过的url地址不会继续被请求,对需要重复请求的url地址可以把它设置为Ture,比如贴吧的翻页请求,页面的数据总是在变化;start_urls中的地址会被反复请求,否则程序不会启动。
- 5:method: 默认是get,如果是post则需要指定。
- 6:headers : 接收一个字典,其中不包括cookies
- 7:cookies: 接收一个字典,专门放置cookies
- 8:body: 接收json字符串,为POST的数据,发送payload_post请求时使用。
四:案例—爬取网易招聘信息:
1: 准备工作:
目的:通过爬取网易招聘的页面的招聘信息,学习如何实现翻页请求。
1: 更改配置文件:
ROBOTSTXT_OBEY = False # 表示忽略robots.txt协议,
USER_AGENT = ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36’ # 设置user-Agent ,每次请求都会携带。
2:创建爬虫:
3:前端分析:
分析前端代码可知,首先要获取tr标签,然后再获取里面的a标签的内容。
分析下一页操作,发现每次生成一页,在网页中的这个标签都会生成下一页的url。
2: 代码实现:
1:模型类:
class JobItem(scrapy.Item):
name = scrapy.Field()
2: 爬虫代码:
import scrapy
from myspider.items import JobItem
class JsbSpiderSpider(scrapy.Spider):
name = 'jsb_spider'
allowed_domains = ['https://hr.163.com/']
start_urls = ['https://hr.163.com/position/list.do']
def parse(self, response):
# 提取响应数据,获取数据中的tr列表
tr_list = response.xpath("//table[@class='position-tb']//tr")
# 去除数据为空的tr标签: 因为我们发现所有的偶数的tr标签都是空的。
tr_list = [tr for tr in tr_list if tr_list.index(tr) % 2 != 0]
# 遍历取出每一条数据:
for tr in tr_list:
item = JobItem()
item['name'] = tr.xpath("./td[1]/a/text()").extract_first()
print(item)
# 获取下一页的url地址:
next_url = response.xpath("//a[text()='>']/@href").extract_first()
if next_url != "javascript:void(0)":
full_url = self.start_urls[0] + next_url
yield scrapy.Request(url=full_url, callback=self.parse, dont_filter=True)
3: 爬虫测试:
爬到173页,网站崩了,不好意思。
五:meta参数的使用:
- 1:meta可以实现在不同的解析函数中的进行参数传递。
分析:
首先,引擎会将首页的响应传递给parse函数,parse函数中,可以先获得职位的列表标签,然后遍历列表标签,拿到列表中的职位名称。然后在标签中获取详情页面的请求,构建请求,使用yield,将请求传给引擎。引擎会将详情页的响应传递给parse_detail,还会把parse函数传递给他的meta字典传递给他。那么在parse_detail中可以根据响应拿到职位的详情信息,可以根据response.meta获取职位名称。
案例:首页获取职位名称,详情页获取发布时间:
import scrapy
from ..items import JobDetaiItem
class JobDetailSpider(scrapy.Spider):
"""
需求:爬取首页的职位名称,然后将item对象传给详情页,爬取详情页的发布时间
"""
name = 'job_detail'
allowed_domains = ['163.com']
start_urls = ['https://hr.163.com/position/list.do']
def parse(self, response):
# 1.从响应对象中提取tr标签列表
tr_list = response.xpath("//table[@class='position-tb']//tr")
# 2.过滤掉空的tr标签
tr_list = [tr for tr in tr_list if tr_list.index(tr) % 2 != 0]
# 3.遍历提取职位名称
for tr in tr_list:
# 创建模型对象
item = JobDetaiItem()
# extract_first() 提取列表中第一条数据
item["name"] = tr.xpath("./td[1]/a/text()").extract_first()
# 往详情页发起请求,抓取数据
detail_url = tr.xpath("./td[1]/a/@href").extract_first()
# 1.构建详情页完整url地址
full_path = "https://hr.163.com/" + detail_url
# 2.构建请求,
request = scrapy.Request(url=full_path,
callback=self.parse_detail,
meta={"item": item})
# 3.并且yield给引擎
yield request
def parse_detail(self, response):
"""
解析详情页面的响应
:param response: 详情页响应对象
:return:
"""
# 1.从响应中获取item模型对象
item = response.meta["item"]
# 2.提取发布时间数据添加到模型对象中
item["date"] = response.xpath("//p[@class='post-date']/text()").extract_first()
print(item)
六:使用scrapy发送post请求:
- 1: 需要重写父类的start_requests方法。
- 2:scrapy.FormRequest(url=url, formdata=post_body, callback=self.parse)
- 3:注意尽量不要使用:scrapy.Request发送post请求。
案例:金山翻译:
1: 创建爬虫:
2:代码展示:
import scrapy
import json
class JinshanSpiderSpider(scrapy.Spider):
name = 'jinshan_spider'
allowed_domains = ['http://fy.iciba.com/']
start_urls = ['http://fy.iciba.com/ajax.php?a=fy']
# 如果要发送post请求,需要重写父类的start_requests方法
def start_requests(self):
# 1:构建请求体字典
post_body = {
"f": "auto",
"t": "auto",
"w": "您好",
}
# 2:构建post请求对象
for url in self.start_urls:
post_request = scrapy.FormRequest(url=url, formdata=post_body, callback=self.parse)
# 交付给引擎
yield post_request
def parse(self, response):
json_str = response.body.decode()
data_dict = json.loads(json_str)
try:
ret = data_dict["content"]["word_mean"]
except:
ret = data_dict["content"]["out"]
print(ret)
七:Scrapy框架携带cookie发送post请求:
- 只需要发送请求的时候携带上Cookie字典:scrapy.Request(url=url, cookies=cookie_dict, callback=self.parse)
案例:模拟github登录:
思路:发送请求的时候携带cookie
import scrapy
class GithubSpider(scrapy.Spider):
name = 'github'
allowed_domains = ['github.com']
start_urls = ['https://github.com/TmacChenQian']
# 重写start_urls起始URL地址构建请求的方法
def start_requests(self):
# 1.准备cookie字符串
cookie_str = "复制cookie字符串"
# 2.cookie字符串转换成字典
cookie_dict = {cookie.split("=")[0]: cookie.split("=")[1] for cookie in cookie_str.split("; ")}
# 3.自己构建一个GET请求,携带cookie参数
for url in self.start_urls:
# 4.yield将请求交给引擎
yield scrapy.Request(url=url, cookies=cookie_dict, callback=self.parse)
# 解析响应
def parse(self, response):
with open("github.html", "w", encoding='utf-8') as f:
f.write(response.body.decode())
八:使用scrapy.Formrequest.from_response登陆github
- scrapy.Formrequest.from_response,能够自动提交表单。
思路:先向登录页面发送请求,获取响应。
然后在响应中定位form表单(formxpath="//div[@id=‘login’]/form",),注意,必须是有登录密码的那个表单。
然后使用 formdata={“login”: “账号”, “password”: “密码”},向表单中增加账号和密码,这里login和password指的是标签的name属性的值。
最后自动提交表单,完成登录请求。
import scrapy
class Github1Spider(scrapy.Spider):
name = 'github1'
allowed_domains = ['github.com']
start_urls = ['https://github.com/login']
def parse(self, response):
# response 响应对象
# formxpath 使用xpath语法提取form表单
# formdata 向表单中填充数据
# callback 回调解析函数
yield scrapy.FormRequest.from_response(response,
formxpath="//div[@id='login']/form",
formdata={"login": "账号", "password": "密码"},
callback=self.login_parse)
def login_parse(self, response):
with open("github6.html", "w", encoding='utf-8') as f:
f.write(response.body.decode())