1. 爬虫流程
获取网页数据 --> 解析数据 --> 保存数据。
1)第一步:确定目标网页;
2)第二步:找数据接口:
a. 有数据接口 --> 直接使用requests对数据接口发送请求 -> 网页数据获取完成
b. 没有数据接口 --> 第三步
3)第三步:用requests直接对网页发送请求(可能涉及到添加user-agent或者添加cookie):
a. 请求结果中有目标数据 --> 网页数据获取完成
b. 请求结果中没有目标数据 --> 第四步
4)第四步:用selenium获取网页内容
a. 结果中有目标数据 -> 网页数据获取完成
b. 结果中没有目标数据 -> 放弃
2. 作业51 job岗位爬虫
from selenium.webdriver import Chrome, ChromeOptions
from selenium.webdriver.common.keys import Keys
import time
if __name__ == '__main__':
# 1. 创建浏览器,打开网站
options = ChromeOptions()
options.add_experimental_option('excludeSwitches', ['enable-automation'])
# options.add_argument("disable-blink-features=AutomationControlled")
b = Chrome(options=options)
b.execute_cdp_cmd(
"Page.addScriptToEvaluateOnNewDocument",
{
"source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
}
)
b.get('https://www.51job.com/')
with open('files/51job.txt', encoding='utf-8') as f:
cookie_list = eval(f.read())
for x in cookie_list:
b.add_cookie(x)
b.get('https://www.51job.com/')
b.implicitly_wait(5)
time.sleep(2)
search = b.find_element_by_id('kwdselectid')
search.send_keys('数据分析')
search.send_keys(Keys.ENTER)
time.sleep(10)
b.close()
3. 作业英雄联盟皮肤爬虫
import requests
import os
# 1. 获取所有英雄的英雄id
def get_all_hero_id():
url = 'https://game.gtimg.cn/images/lol/act/img/js/heroList/hero_list.js'
res = requests.get(url)
return [x['heroId'] for x in res.json()['hero']]
# 下载图片
def download_image(img_url, path):
res = requests.get(img_url)
with open(path, 'bw') as f:
f.write(res.content)
print(f'{img_url}下载成功!')
# 2. 获取指定英雄的皮肤数据
def get_skins(hero_id):
url = f'https://game.gtimg.cn/images/lol/act/img/js/hero/{hero_id}.js'
res = requests.get(url)
data = res.json()
# 获取英雄名称
hero_name = data['hero']['name']
# 创建英雄名称对应的文件夹
os.mkdir(f'所有英雄皮肤/{hero_name}')
# 遍历拿到每个皮肤
for skin in data['skins']:
s_name = skin.get('name').replace('/', '')
img_url = skin.get('mainImg')
# 如果图片地址存在
if img_url:
# 下载图片
download_image(img_url, f'所有英雄皮肤/{hero_name}/{s_name}.jpg')
if __name__ == '__main__':
# 获取所有英雄的id
all_id = get_all_hero_id()
# 遍历列表拿到每个英雄的id,请求对应的英雄皮肤
for x in all_id:
get_skins(x)
4. 多线程基础
4.1 进程
进程: 一个正在运行的应用程序就是一个进程。
每个进程均运行在其专用且受保护的内存空间中。(进程结束,内存会自动释放)
4.2 线程
线程是进程执行任务的基本单元。
如果一个进程需要执行任务,必须要线程。所有的任务都必须在线程中执行。
进程 - 车间(工厂)
线程 - 工人
4.3 线程的串行
如果在一个线程中执行多个任务,多个任务是串行执行的(一个一个按顺序执行)。
4.4 多线程
一个进程中默认只有一个线程
1)多线程: 一个进程中同时拥有多个线程。
2)多线程特点:多个线程执行多个任务,是并行(同时)执行的。
3)多线程可以提高程序的执行效率,但是并不是线程数越多越好。一般情况下:手机应用程序一般3~5个、电脑程序几十个到两三百个(要分析具体情况)。
4.5 多线程原理
1)多线程的存在可以让一个程序同一时间执行不同的任务。
2)一个cpu同一时间只能执行一个线程。多线程其实是cpu在多个线程之间调度(切换),当调度速度足够快的时候就会造成多个线程同时执行的假象。
5. 多线程基本用法
python程序默认只有一个线程(这个线程叫主线程),除了主线程以外需要别的线程(子线程),必须用代码来创建线程对象(Thread的对象)。
默认情况下,程序中的代码都是在主线程中执行。
5.1 导入线程类
from threading import Thread
from datetime import datetime
import time
def download(m_url):
print(f'---------------{m_url}开始下载:{datetime.now()}-----------------')
time.sleep(2)
print(f'---------------{m_url}下载结束:{datetime.now()}-----------------')
5.2 线程的串行
download('肖生克的救赎')
download('许三观卖血记')
download('沉默的羔羊')
5.3 多线程的并行
5.3.1 创建线程对象,并且给子线程添加任务
Thread(target=函数,args=元组)。
1)函数 - 需要在子线程中执行的任务。
2)元组 - 元组中的元素就是target对应的函数在调用的时候需要的参数。
t1 = Thread(target=download, args=('肖生克的救赎',))
t2 = Thread(target=download, args=('许三观卖血记',))
t3 = Thread(target=download, args=('沉默的羔羊',))
5.3.2 启动线程
启动线程:线程对象.start()。
t1.start()
t2.start()
t3.start()
download('霸王别姬')
5.4 批量创建子线程
names = [f'电影{x}' for x in range(1,11)]
for name in names:
t = Thread(target=download, args=(name,))
t.start()
6. 豆瓣电影多线程爬虫
from threading import Thread
import requests
from lxml import etree
import csv
import datetime
headers = {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36'
}
def get_one_page(start):
print(f'========{start}开始下载:{datetime.datetime.now()}===============')
# 获取网页数据
url = f'https://movie.douban.com/top250?start={start}&filter='
response = requests.get(url, headers=headers)
# 解析数据
html = etree.HTML(response.text)
all_li = html.xpath('//ol[@class="grid_view"]/li')
page_data = []
for li in all_li:
name = li.xpath('./div/div[2]/div[1]/a/span[1]/text()')[0]
score = li.xpath('./div/div[2]/div[2]/div/span[@class="rating_num"]/text()')[0]
comment = li.xpath('./div/div[2]/div[2]/div/span[last()]/text()')[0]
page_data.append([name, score, comment])
# 保存数据
writer.writerows(page_data)
print(f'========{start}下载结束:{datetime.datetime.now()}===============')
if __name__ == '__main__':
writer = csv.writer(open('files/电影.csv', 'w', encoding='utf-8', newline=''))
writer.writerow(['名字', '分数', '评论人数'])
for x in range(0, 226, 25):
t = Thread(target=get_one_page, args=(x,))
t.start()
7. 阻塞
7.1 join的用法
多线程编程的时候,如果一个线程中需要等待另外的子线程中的任务结束才执行某个操作,就可以在需要等待的位置用子线程对象调用join方法。
注意:如果只需要等待一个子线程任务完成,就用一个子线程对象调用join,如果需要等多个子线程的任务完成,就用多个线程对象调用join
rom threading import Thread
from datetime import datetime
import time, random
def download(m_url):
print(f'--------------{m_url}开始下载:{datetime.now()}------------------')
time.sleep(random.randint(2, 7))
print(f'--------------{m_url}下载结束:{datetime.now()}------------------')
t1 = Thread(target=download, args=('肖生克的救赎',))
t2 = Thread(target=download, args=('许三观卖血记',))
t3 = Thread(target=download, args=('沉默的羔羊',))
1)案例1:等待所有的子线程任务都结束,打印全部下载完成!
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
print('全部下载完成!')
2)案例2: 第一个电影下载结束后才同时下载第2个和第3个电影
t1.start()
t1.join()
t2.start()
t3.start()
3)案例3:同时下载10个电影,要求10个电影都下载结束的时候打印 “全部下载完成!”
ts = []
for x in range(1, 11):
t = Thread(target=download, args=(f'电影{x}', ))
t.start()
ts.append(t)
for t in ts:
t.join()
print('全部下载完成!')
8. 作业1
作业1:用多线程同时获取豆瓣top250的10页数据并且解析保存,但是要求存储到csv文件中的数据和网页中电影的顺序一致。
headers = {
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36'
}
page_data = []
def get_one_page(start):
print(f'========{start}开始下载:{datetime.datetime.now()}===============')
# 获取网页数据
url = f'https://movie.douban.com/top250?start={start}&filter='
response = requests.get(url, headers=headers)
# 解析数据
html = etree.HTML(response.text)
all_li = html.xpath('//ol[@class="grid_view"]/li')
for li in all_li:
# //*[@id="content"]/div/div[1]/ol/li[1]/div/div[1]/em
order = li.xpath('./div/div[1]/em/text()')[0]
# print(order, type((order)))
name = li.xpath('./div/div[2]/div[1]/a/span[1]/text()')[0]
score = li.xpath('./div/div[2]/div[2]/div/span[@class="rating_num"]/text()')[0]
comment = li.xpath('./div/div[2]/div[2]/div/span[last()]/text()')[0]
href = li.xpath('./div/div[1]/a/@href')[0]
# print(href,type(href))
page_data.append([order, name, score, comment, href])
print(f'========{start}下载结束:{datetime.datetime.now()}===============')
if __name__ == '__main__':
writer = csv.writer(open('files/电影.csv', 'w', encoding='utf-8', newline=''))
writer.writerow(['序号', '名字', '分数', '评论人数', '链接'])
all_t = []
for x in range(0, 226, 25):
t = Thread(target=get_one_page, args=(x,))
t.start()
all_t.append(t)
for t in all_t:
t.join()
# 保存数据
page_data.sort( key=lambda item: int(item[0]))
print(page_data)
writer.writerows(page_data)
9. 作业2
作业2:用多线程同时获取京东3种商品的10页数据并且保存。
from selenium.webdriver import Chrome,ChromeOptions
from selenium.webdriver.common.keys import Keys
from threading import Thread
import time
from bs4 import BeautifulSoup
import csv
from tqdm import tqdm
def get_one_page(commodity, num1):
writer = csv.writer(open(f'files/京东商品{commodity}.csv', 'a', encoding='utf-8', newline=''))
# 1. 创建浏览器
options = ChromeOptions()
# 取消测试环境
options.add_experimental_option('excludeSwitches', ['enable-automation'])
# 取消图片加载
options.add_experimental_option("prefs", {"profile.managed_default_content_settings.images": 2})
# 创建浏览器
b = Chrome(options=options)
# 2. 打开京东搜索商品
b.get('https://www.jd.com')
time.sleep(1)
# 控制输入框输入内容
# 获取输入框
search = b.find_element_by_id('key')
# 输入内容
search.send_keys(commodity)
# 按回车
search.send_keys(Keys.ENTER)
time.sleep(1)
# 1. 页面滚动加载所有数据
for _ in range(num1):
for _ in range(10):
b.execute_script('window.scrollBy(0,1000)')
time.sleep(1)
soup = BeautifulSoup(b.page_source, 'lxml')
# print(soup)
goods_li = soup.select('#J_goodsList>ul.gl-warp.clearfix>li')
for li in goods_li:
# 价格
# #J_goodsList > ul > li:nth-child(1) > div > div.p-price > strong > i
if li.select_one('div > div.p-price > strong > i'):
price = '¥' + li.select_one('div > div.p-price > strong > i').text
else:
price = None
# 商品名称
goods_name = li.select_one('div.p-name.p-name-type-2>a>em').text
# 商品链接
good_href = 'https:' + li.select_one('div.p-name.p-name-type-2>a').attrs['href']
# 店铺名称
shop_name = li.select_one('span.J_im_icon>a').text
# 店铺链接
shop_href = 'https:' + li.select_one('span.J_im_icon>a').attrs['href']
# 评价
# #J_comment_10050742233436
# #J_goodsList > ul > li:nth-child(1) > div > div.p-commit > strong
if li.select_one('div > div.p-commit > strong'):
comment = li.select_one('div > div.p-commit > strong').text
else:
comment = None
# print(price, goods_name, good_href, shop_name, shop_href, comment)
writer.writerow([price, goods_name, good_href, shop_name, shop_href, comment])
# 4. 跳转到下一页
next = b.find_element_by_class_name('pn-next')
next.click()
time.sleep(1)
if __name__ == '__main__':
# get_one_page('衣服')
ts = []
for x in tqdm(['衣服', '裤子', '鞋子'], desc='进度条'):
writer = csv.writer(open(f'files/京东商品{x}.csv', 'w', encoding='utf-8', newline=''))
writer.writerow(['价格', '商品名称', '商品链接', '店铺名称', '店铺链接', '评价'])
t = Thread(target=get_one_page, args=(x, 10))
t.start()
ts.append(t)
for t in ts:
t.join()