一、基本流程总结
1.准备url
-
准备start_url
使用情况:url地址规律不明显,总数不确定
具体做法:通过代码提取下一页的url,可通过 xpath 寻找url地址,部分参数在当前的响应中(比如,当前页码数和总的页码数在当前的响应中) -
准备url_list
使用情况:页码总数明确,url地址规律明显
2.发送请求,获取响应
- 反反爬虫
(1)添加随机的User-Agent,反反爬虫
(2)添加随机的代理ip,反反爬虫
注:
①在对方判断出我们是爬虫之后,应该添加更多的headers字段,包括cookie( cookie的处理可以使用session来解决,requests.Session(),session.get(url))
②准备一堆能用的cookie,组成cookie池
- 不登录请求
(1)准备刚开始能够成功请求对方网站的cookie,即接收对方网站设置在response的cookie
(2) 下一次请求的时候,使用之前的列表中的cookie来请求 - 登录后的请求
(1)准备多个账号
(2)使用程序获取每个账号的cookie,之后请求登录之后才能访问的网站随机的选择cookie
3.提取数据
- 如果数据不在当前的url的响应中
- 确定数据的位置
(1) 提取的是列表页的数据
直接请求列表页的url地址,不用进入详情页
(2) 提取的是详情页的数据
.确定url
.发送请求
.提取数据
.返回数据
- 确定数据的位置
- 如果数据不在当前的url地址中- 在其他的响应中,寻找数据的位置(从其他url响应中查找数据)
(1) 从network中从上往下找,并在响应数据中通过ctr+f过滤来查找
(2)使用chrome中的过滤条件,选择出了js,css,img之外的按钮
(3)使用chrome的search all file,搜索数字和英文 - 提取数据的方式
(1)xpath,从html中提取整块的数据,先分组,之后每一组再提取
(2)re正则表达式,通过re.search/findall等匹配方式匹配爬取的数据
(3json
4.保存
- 保存在本地,text,json,csv
- 保存在数据库
二、单线程爬取嗅事百科段子案例
- 爬取嗅事百科段子每一页上,每条帖子的发帖人昵称,性别,年龄,发帖内容,帖子好笑数
import requests
from lxml import etree
import json
class XiuBai():
def __init__(self):
self.url_list = ['http://www.qiushibaike.com/text/page/%d/'%i for i in range(1,14)]
self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36'}
def parse_url(self,url):
'''发送请求,获取响应'''
print(url)
response = requests.get(url,headers=self.headers)
# print(response.content.decode())
return response.content.decode() # 返回字符串类型
def get_content(self,html_str):
'''提取数据(使用xpath方式提取)'''
content_list = [] # 用于存储每一页的所有帖子内容
#创建element对象
html = etree.HTML(html_str)
# 分组,每个div是一条帖子,返回列表类型
div_list = html.xpath("//div[@class='col1 old-style-col1']/div")
# 遍历每一条帖子div
for div in div_list:
# 将每条帖子的相关内容存储在字典中
item = {}
#1.提取每个发帖人的昵称
item['name'] = div.xpath('./div[@class="author clearfix"]//img/@alt')
item['name'] = item['name'][0] if len(item['name'])>0 else None
#2.提取每个发帖人的年龄
item['age'] = div.xpath('./div[@class="author clearfix"]/div/text()')
item['age'] = item['age'][0] if len(item['age'])>0 else None
#3.提取每个发帖人的性别
item['gender'] = div.xpath('./div[@class="author clearfix"]/div/@class')
item['gender'] = item['gender'][0].split(' ')[-1].replace('Icon','') if len(item['gender'])> 0 else None
#4.提取每个帖子的内容
item['content'] = div.xpath('./a/div[@class="content"]/span/text()')
item['content'] = item['content'][0].replace(r'\n','') if len(item['content'])> 0 else None
#5.提取每个帖子中的好笑数
item['smlie_num'] = div.xpath('./div[@class="stats"]//i[@class="number"]/text()')
item['smlie_num'] = item['smlie_num'][0] if len(item['smlie_num'])> 0 else None
content_list.append(item)
return content_list
def save_file(self,content):
'''保存数据'''
with open('qiubai.txt','a',encoding='utf-8',newline='') as f:
f.write(json.dumps(content,ensure_ascii=False,indent=2))
f.write('\n')
def run(self):
'''完成整体的控制'''
#1. 创建url列表
for url in self.url_list:
#2.发送请求,获取响应
html_str = self.parse_url(url)
#3.提取数据
content_list = self.get_content(html_str)
#4.保存数据
self.save_file(content_list)
xiubai = XiuBai()
xiubai.run()
qiubai.txt
三、多线程爬取数据
- 使用Thread实现多线程发送请求,提取数据
- 使用Queue队列实现对线程间的通信
import requests
from lxml import etree
import json
import threading
from queue import Queue
class XiuBai():
def __init__(self):
self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36'}
self.start_url = 'http://www.qiushibaike.com/text/page/{}/'
self.url_queue = Queue()
self.html_queue = Queue()
self.content_queue = Queue()
def create_url_list(self):
'''创建url列表'''
for i in range(1,14):
# 将url保存在self.url_queue队列中
self.url_queue.put(self.start_url.format(i))
def parse_url(self):
'''发送请求,获取响应'''
while True:
#将url从队列Q中取出
url = self.url_queue.get()
print(url)
response = requests.get(url,headers=self.headers)
# 将响应数据保存在队列中
self.html_queue.put(response.content.decode())
self.url_queue.task_done()
def get_content(self):
'''提取数据(使用xpath方式提取)'''
while True:
# 将相应数据从队列Q中取出
html_str = self.html_queue.get()
content_list = [] # 用于存储每一页的所有帖子内容
#创建element对象
html = etree.HTML(html_str)
# 分组,每个div是一条帖子,返回列表类型
div_list = html.xpath("//div[@class='col1 old-style-col1']/div")
# 遍历每一条帖子div
for div in div_list:
# 将每条帖子的相关内容存储在字典中
item = {}
#1.提取每个发帖人的昵称
item['name'] = div.xpath('./div[@class="author clearfix"]//img/@alt')
item['name'] = item['name'][0] if len(item['name'])>0 else None
#2.提取每个发帖人的年龄
item['age'] = div.xpath('./div[@class="author clearfix"]/div/text()')
item['age'] = item['age'][0] if len(item['age'])>0 else None
#3.提取每个发帖人的性别
item['gender'] = div.xpath('./div[@class="author clearfix"]/div/@class')
item['gender'] = item['gender'][0].split(' ')[-1].replace('Icon','') if len(item['gender'])> 0 else None
#4.提取每个帖子的内容
item['content'] = div.xpath('./a/div[@class="content"]/span/text()')
item['content'] = item['content'][0].replace(r'\n','') if len(item['content'])> 0 else None
#5.提取每个帖子中的好笑数
item['smlie_num'] = div.xpath('./div[@class="stats"]//i[@class="number"]/text()')
item['smlie_num'] = item['smlie_num'][0] if len(item['smlie_num'])> 0 else None
content_list.append(item)
# 将数据存在self.content_queue队列中
self.content_queue.put(content_list)
self.html_queue.task_done()
def save_file(self):
'''保存数据'''
while True:
with open('qiubai.txt','a',encoding='utf-8',newline='') as f:
# 将数据从队列Q中取出
content = self.content_queue.get()
f.write(json.dumps(content,ensure_ascii=False,indent=2))
f.write('\n')
self.content_queue.task_done()
def run(self):
'''完成整体的控制'''
thread_list = []
#1. 创建url列表
url_th = threading.Thread(target=self.create_url_list)
thread_list.append(url_th)
#2.发送请求,获取响应
parse_th = threading.Thread(target=self.parse_url)
thread_list.append(parse_th)
#3.提取数据
content_th = threading.Thread(target=self.get_content)
thread_list.append(content_th)
#4.保存数据
save_th = threading.Thread(target=self.save_file)
thread_list.append(save_th)
# 启动线程
for t in thread_list:
t.setDaemon(True) #把子线程设置为守护线程,该线程不重要主线程结束,子线程结束
t.start()
for q in [self.url_queue,self.html_queue ,self.content_queue]:
q.join() #让 主线程等待阻塞,等待队列的任务完成之后再完成
xiubai = XiuBai()
xiubai.run()