关于我 壹文 项目 三五好友
Python 爬虫学习笔记 2024-09-04

注:该文章为个人学习笔记

原文章链接为:https://github.com/wistbean/learn_python3_spider?tab=readme-ov-file

01 Reuqests库

一行代码 Get 请求

r = requests.get('https://api.github.com/events')

一行代码 Post 请求

r = requests.post('https://httpbin.org/post', data = {'key':'value'})

其它乱七八糟的 Http 请求

>>> 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')

想要携带请求参数是吧?

>>> payload = {'key1': 'value1', 'key2': 'value2'}

>>> r = requests.get('https://httpbin.org/get', params=payload)

假装自己是浏览器

>>> url = 'https://api.github.com/some/endpoint'

>>> headers = {'user-agent': 'my-app/0.0.1'}

>>> r = requests.get(url, headers=headers)

获取服务器响应文本内容

会自动将字节内容解码为字符串,通常使用 UTF-8 编码(如果服务器没有指定编码,requests 会尝试自动检测编码)。

  • 解码response.text 会根据响应头中的 Content-Type 字段自动解码字节内容。
  • 类型:返回的是一个字符串(str 类型)。
  • 适用场景:适用于处理文本数据,如 HTML、JSON、XML 等。
>>> import requests

>>> r = requests.get('https://api.github.com/events')

>>> r.text

u'[{"repository":{"open_issues":0,"url":"https://github.com/...
>>> r.encoding

'utf-8'

获取字节响应内容

  • 原始数据response.content 返回的是原始的字节数据,不进行解码。
  • 类型:返回的是一个字节串(bytes 类型)。
  • 适用场景:适用于处理二进制数据,如图片、音频、视频文件,或者当你需要手动处理编码时。
>>> r.content

b'[{"repository":{"open_issues":0,"url":"https://github.com/...

获取响应码

>>> r = requests.get('https://httpbin.org/get')

>>> r.status_code

200

获取响应头

>>> r.headers

{
    'content-encoding': 'gzip',
    'transfer-encoding': 'chunked',
    'connection': 'close',
    'server': 'nginx/1.0.4',
    'x-runtime': '148ms',
    'etag': '"e1ca502697e5c9317743dc078f67693f"',
    'content-type': 'application/json'

}

获取 Json 响应内容

>>> import requests

>>> r = requests.get('https://api.github.com/events')

>>> r.json()

[{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...

获取 socket 流响应内容

>>> r = requests.get('https://api.github.com/events', stream=True)

>>> r.raw

<urllib3.response.HTTPResponse object at 0x101194810>

>>> r.raw.read(10)

'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'

Post请求

当你想要一个键里面添加多个值的时候

>>> payload_tuples = [('key1', 'value1'), ('key1', 'value2')]

>>> r1 = requests.post('https://httpbin.org/post', data=payload_tuples)

>>> payload_dict = {'key1': ['value1', 'value2']}

>>> r2 = requests.post('https://httpbin.org/post', data=payload_dict)

>>> print(r1.text)

{  ...  "form": {    "key1": [      "value1",      "value2"    ]  },  ...}

>>> r1.text == r2.text

True

请求的时候用 json 作为参数

>>> url = 'https://api.github.com/some/endpoint'

>>> payload = {'some': 'data'}

>>> r = requests.post(url, json=payload)

想上传文件?

  • r:表示以“只读”模式打开文件。你可以读取文件的内容,但不能修改它。
  • b:表示以“二进制”模式打开文件。这意味着文件将以二进制格式读取,而不是文本格式。二进制模式适用于非文本文件,如图片、音频、视频和某些数据文件(如 Excel 文件)。
>>> url = 'https://httpbin.org/post'

>>> files = {'file': open('report.xls', 'rb')}

>>> r = requests.post(url, files=files)

>>> r.text

{  ...  "files": {    "file": "<censored...binary...data>"  },  ...}
>>> url = 'http://example.com/some/cookie/setting/url'

>>> r = requests.get(url)

>>> r.cookies['example_cookie_name']

'example_cookie_value'

这行代码的作用是创建一个字典(dict),其中包含一个键值对。

>>> url = 'https://httpbin.org/cookies'

>>> cookies = dict(cookies_are='working')

>>> r = requests.get(url, cookies=cookies)

>>> r.text

'{"cookies": {"cookies_are": "working"}}'

设置超时

>>> requests.get('https://github.com/', timeout=0.001)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>requests.exceptions.Timeout: HTTPConnect

02 正则表达式

使用场景:在服务器返回给我们的源码之中,我们要过滤,拿到我们想要的就好,其它就丢一旁

需要的 Python 库:re

字符描述
d代表任意数字,就是阿拉伯数字 0-9 这些玩意。
D大写的就是和小写的唱反调,d 你代表的是任意数字是吧?那么我 D 就代表不是数字的。
w代表字母,数字,下划线。也就是 a-z、A-Z、0-9、_。
W跟 w 唱反调,代表不是字母,不是数字,不是下划线的。
n代表一个换行。
r代表一个回车。
f代表换页。
t代表一个 Tab 。
s代表所有的空白字符,也就是上面这个:n、r、t、f。
S跟 s 唱反调,代表所有不是空白的字符。
A代表字符串的开始。
Z代表字符串的结束。
^匹配字符串开始的位置。
$匹配字符创结束的位置。
.代表所有的单个字符,除了 n r
[...]代表在 [] 范围内的字符,比如 [a-z] 就代表 a到z的字母
[^...]跟 […] 唱反调,代表不在 [] 范围内的字符
{n}匹配在 {n} 前面的东西,比如: o{2} 不能匹配 Bob 中的 o ,但是能匹配 food 中的两个o。
{n,m}匹配在 {n,m} 前面的东西,比如:o{1,3} 将匹配“fooooood”中的前三个o。
{n,}匹配在 {n,} 前面的东西,比如:o{2,} 不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。
*和 {0,} 一个样,匹配 _ 前面的 0 次或多次。 比如 zo_ 能匹配“z”、“zo”以及“zoo”。
+和{1,} 一个样,匹配 + 前面 1 次或多次。 比如 zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。
和{0,1} 一个样,匹配 ?前面 0 次或 1 次。
a|b匹配 a 或者 b。
()匹配括号里面的内容。

以下是重新整理的 BeautifulSoup 使用笔记,采用了 Markdown 文档结构并增加了趣味性,便于理解与记忆:


03 BeautifulSoup —— 让网页解析更简单

BeautifulSoup 是一个强大的 Python 库,可以从 HTML 或 XML 文件中提取数据。相比直接用正则表达式来匹配数据,使用 BeautifulSoup 更加高效简洁!

1. 安装 BeautifulSoup

首先需要安装 BeautifulSoup4 和 lxml 解析器:

pip install beautifulsoup4
pip install lxml

2. 使用 BeautifulSoup 解析 HTML

下面是一个示例 HTML 代码,我们将用 BeautifulSoup 来提取其中的内容:

html_doc = """
<html>
  <head>
    <title>学习python的正确姿势</title>
  </head>
  <body>
    <p class="title"><b>小帅b的故事</b></p>
    <p class="story">
      有一天,小帅b想给大家讲两个笑话
      <a href="http://example.com/1" class="sister" id="link1">一个笑话长</a>,
      <a href="http://example.com/2" class="sister" id="link2">一个笑话短</a> ,
      他问大家,想听长的还是短的?
    </p>
    <p class="story">...</p>
    """
  </body>
</html>

将 HTML 源代码传给 BeautifulSoup:

from bs4 import BeautifulSoup

soup = BeautifulSoup(html_doc, 'lxml')

3. 从 BeautifulSoup 对象中提取内容

获取标题内容

print(soup.title.string)  # 输出:学习python的正确姿势

获取第一个 <p> 标签中的内容

print(soup.p.string)  # 输出:小帅b的故事

获取 <title> 标签的父级标签

print(soup.title.parent.name)  # 输出:head

获取第一个超链接

print(soup.a)  # 输出:<a class="sister" href="http://example.com/1" id="link1">一个笑话长</a>

获取所有超链接

print(soup.find_all('a'))
# 输出:[<a class="sister" href="http://example.com/1" id="link1">一个笑话长</a>, <a class="sister" href="http://example.com/2" id="link2">一个笑话短</a>]

获取 id 为 link2 的超链接

print(soup.find(id="link2"))  # 输出:<a class="sister" href="http://example.com/2" id="link2">一个笑话短</a>

获取网页中的所有文本内容

print(soup.get_text())

输出:

学习python的正确姿势
小帅b的故事
有一天,小帅b想给大家讲两个笑话
一个笑话长,
一个笑话短 ,
他问大家,想听长的还是短的?
...

4. 使用 CSS 选择器来提取内容

如果熟悉 CSS 选择器,可以使用 select 方法:

print(soup.select("title"))          # 选择 `<title>` 标签
print(soup.select("body a"))         # 选择 `<body>` 标签中的所有 `<a>` 标签
print(soup.select("p > #link1"))     # 选择 `<p>` 标签下 id 为 `link1` 的标签

5. BeautifulSoup 实战:解析电影数据

以下是一个从豆瓣电影页面解析电影信息的示例:

示例 HTML 结构

<ol class="grid_view">
  <li>
    <div class="item">
      <div class="pic">
        <em class="">1</em>
        <a href="https://movie.douban.com/subject/1292052/">
          <img
            width="100"
            alt="肖申克的救赎"
            src="https://img3.doubanio.com/view/photo/s_ratio_poster/public/p480747492.jpg"
            class=""
          />
        </a>
      </div>
      <div class="info">
        <div class="hd">
          <a href="https://movie.douban.com/subject/1292052/" class="">
            <span class="title">肖申克的救赎</span>
            <span class="title">&nbsp;/&nbsp;The Shawshank Redemption</span>
            <span class="other">&nbsp;/&nbsp;月黑高飞(港) / 刺激1995(台)</span>
          </a>
          <span class="playable">[可播放]</span>
        </div>
        <div class="bd">
          <p class="">
            导演: 弗兰克·德拉邦特 Frank Darabont&nbsp;&nbsp;&nbsp;主演: 蒂姆·罗宾斯 Tim Robbins
            /...<br />
            1994&nbsp;/&nbsp;美国&nbsp;/&nbsp;犯罪 剧情
          </p>
          <div class="star">
            <span class="rating5-t"></span>
            <span class="rating_num" property="v:average">9.6</span>
            <span property="v:best" content="10.0"></span>
            <span>1286755人评价</span>
          </div>
          <p class="quote">
            <span class="inq">希望让人自由。</span>
          </p>
        </div>
      </div>
    </div>
  </li>
</ol>

使用 BeautifulSoup 解析数据

# 假设 soup 已经包含上面的 HTML 内容
list = soup.find(class_='grid_view').find_all('li')

for item in list:
    item_name = item.find(class_='title').string
    item_img = item.find('a').find('img').get('src')
    item_index = item.find('em').string
    item_score = item.find(class_='rating_num').string
    item_author = item.find('p').text
    item_intr = item.find(class_='inq').string

    print(f'爬取电影:{item_index} | {item_name} | {item_score} | {item_intr}')

6. 将数据存储到 Excel

导入 Excel 库

import xlwt

创建 Excel 文件和 Sheet

book = xlwt.Workbook(encoding='utf-8', style_compression=0)
sheet = book.add_sheet('豆瓣电影Top250', cell_overwrite_ok=True)

# 设置表头
sheet.write(0, 0, '名称')
sheet.write(0, 1, '图片')
sheet.write(0, 2, '排名')
sheet.write(0, 3, '评分')
sheet.write(0, 4, '作者')
sheet.write(0, 5, '简介')

写入爬取的数据

n = 1
for item in list:
    sheet.write(n, 0, item_name)
    sheet.write(n, 1, item_img)
    sheet.write(n, 2, item_index)
    sheet.write(n, 3, item_score)
    sheet.write(n, 4, item_author)
    sheet.write(n, 5, item_intr)
    n += 1

保存 Excel 文件

book.save('豆瓣最受欢迎的250部电影.xlsx')

04 Selenium —— 自动执行的爬虫利器

Selenium 是一个自动化测试工具,支持各种主流浏览器。当它遇到 Python 时,便成为了强大的爬虫工具!

1. 处理动态内容

  • Selenium:能够处理动态加载的网页内容,特别是那些使用 JavaScript 渲染的页面。许多现代网站使用 JavaScript 来动态生成内容,Selenium 可以模拟用户在浏览器中的操作,从而获取这些动态生成的数据。
  • requests:只能获取静态 HTML 内容,无法处理 JavaScript 动态生成的内容。

2. 模拟用户行为

  • Selenium:可以模拟用户在浏览器中的各种操作,如点击按钮、填写表单、滚动页面等。这使得它非常适合需要用户交互的场景,例如登录、提交表单等。
  • requests:只能发送 HTTP 请求,无法模拟用户的交互行为。

1. 安装 Selenium

首先,安装 Selenium 库:

pip install selenium

下载浏览器驱动

根据使用的浏览器下载对应的驱动:

浏览器驱动下载链接
Chrome下载地址
Edge下载地址
Firefox下载地址
Safari下载地址

2. 基本使用

导入 Selenium 并启动浏览器:

# 导入 web 驱动模块
from selenium import webdriver

# 创建一个 Chrome 驱动实例
driver = webdriver.Chrome()

# 使用 get 方法打开百度
driver.get("https://www.baidu.com")

# 找到搜索输入框,输入关键字
input = driver.find_element_by_css_selector('#kw')
input.send_keys("苍老师照片")

# 找到搜索按钮并点击
button = driver.find_element_by_css_selector('#su')
button.click()

运行以上代码后,浏览器将自动打开并进行百度搜索!

3. 查找页面元素的方法

Selenium 提供了多种方法来查找页面元素:

  • 单个元素查找

    • find_element_by_id
    • find_element_by_name
    • find_element_by_xpath
    • find_element_by_link_text
    • find_element_by_partial_link_text
    • find_element_by_tag_name
    • find_element_by_class_name
    • find_element_by_css_selector
  • 多个元素查找

    • find_elements_by_name
    • find_elements_by_xpath
    • find_elements_by_link_text
    • find_elements_by_partial_link_text
    • find_elements_by_tag_name
    • find_elements_by_class_name
    • find_elements_by_css_selector

示例用法

通过 id 获取元素

login_form = driver.find_element_by_id('loginForm')

通过 name 获取输入框

username = driver.find_element_by_name('username')
password = driver.find_element_by_name('password')

通过 XPath 获取表单

login_form = driver.find_element_by_xpath("/html/body/form[1]")
login_form = driver.find_element_by_xpath("//form[1]")
login_form = driver.find_element_by_xpath("//form[@id='loginForm']")

通过标签获取元素

input1 = driver.find_element_by_tag_name('input')

通过 class 获取元素

login = driver.find_element_by_class_name('login')

使用 By 简化查找

如果觉得 find_element_by_XXX 太长,还可以使用 By 来简化:

from selenium.webdriver.common.by import By

driver.find_element(By.ID, 'xxx')

By 的属性与上述查找方法相同:

ID = "id"
XPATH = "xpath"
LINK_TEXT = "link text"
PARTIAL_LINK_TEXT = "partial link text"
NAME = "name"
TAG_NAME = "tag name"
CLASS_NAME = "class name"
CSS_SELECTOR = "css selector"

4. 获取页面信息

既然是爬虫,获取源代码当然是必须的!以下是一些获取页面信息的方法:

获取请求链接

print(driver.current_url)

获取 Cookies

print(driver.get_cookies())

获取源代码

print(driver.page_source)

获取元素文本值

print(input.text)

以下是整理后的 PhantomJS 使用笔记,采用 Markdown 文档结构,并增强了趣味性,便于更好地理解和使用:


05 PhantomJS —— 打造无形的浏览器

有时候,我们不希望 Selenium 自动化操作时打开浏览器界面,那能不能在代码里面直接执行呢?答案是肯定的!PhantomJS 就是一个无形的浏览器,它基于 WebKit 引擎,支持 JavaScript、CSS 选择器和 DOM 操作,是爬取动态网站的利器。

1. 为什么选择 PhantomJS?

PhantomJS 是一个无头(Headless)浏览器,意味着它不会在屏幕上显示出实际的浏览器窗口,但仍能像普通浏览器一样执行 JavaScript。这在爬取动态渲染的网页时非常有用。

使用 selenium + phantomjs,你可以做到无声无息地操作各种动态网站。

2. PhantomJS 使用示例

启动浏览器

首先,我们需要拿到浏览器对象:

# 创建一个 Chrome 驱动实例
browser = webdriver.Chrome()

接着访问 B 站:

browser.get("https://www.bilibili.com/")

获取页面元素

我们需要获取 B 站首页的输入框和搜索按钮:

input = WAIT.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#banner_link > div > div > form > input")))
submit = WAIT.until(EC.element_to_be_clickable((By.XPATH, '//*[@id="banner_link"]/div/div/form/button')))

WAIT.until(EC.presence_of_element_located…) 表示等待元素加载完成并可操作。

执行搜索操作

在获取到输入框元素后,我们输入关键字 “蔡徐坤 篮球” 并点击搜索按钮:

input.send_keys('蔡徐坤 篮球')
submit.click()

处理弹出的登录框

如果 B 站弹出了一个登录框,可以在搜索之前点击首页刷新一下再进行输入:

# 被登录框遮住了
index = WAIT.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "#primary_menu > ul > li.home > a")))
index.click()

切换到新窗口

搜索结果会在新窗口中展示,我们需要切换到这个新窗口:

# 跳转到新窗口
print('跳转到新窗口')
all_h = browser.window_handles
browser.switch_to.window(all_h[1])
get_source()

获取总页数

获取搜索结果的总页数,以便抓取所有页面内容:

total = WAIT.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#server-search-app > div.contain > div.body-contain > div > div.page-wrap > div > ul > li.page-item.last > button")))

return int(total.text)

循环抓取所有页面

根据总页数,循环抓取每一页的数据:

for i in range(2, int(total + 1)):
    next_page(i)

获取下一页

模拟点击「下一页按钮」来获取下一页的数据:

print('获取下一页数据')
next_btn = WAIT.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#server-search-app > div.contain > div.body-contain > div > div.page-wrap > div > ul > li.page-item.next > button')))
next_btn.click()
WAIT.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '#server-search-app > div.contain > div.body-contain > div > div.page-wrap > div > ul > li.page-item.active > button'), str(page_num)))
get_source()

异常处理

在某些页面数据加载不出来时,可以捕获异常并刷新页面重试:

except TimeoutException:
    browser.refresh()
    return next_page(page_num)

获取页面资源

使用 Selenium 跳转到目标页面后,我们可以获取当前页面的源码并用 BeautifulSoup 解析:

WAIT.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#server-search-app > div.contain > div.body-contain > div > div.result-wrap.clearfix')))
html = browser.page_source
soup = BeautifulSoup(html, 'lxml')
save_to_excel(soup)

保存数据到 Excel

解析爬取的数据并保存到 Excel 文件中:

def save_to_excel(soup):
    list = soup.find(class_='all-contain').find_all(class_='info')
    for item in list:
        item_title = item.find('a').get('title')
        item_link = item.find('a').get('href')
        item_dec = item.find(class_='des hide').text
        item_view = item.find(class_='so-icon watch-num').text
        item_biubiu = item.find(class_='so-icon hide').text
        item_date = item.find(class_='so-icon time').text

        print('爬取:' + item_title)

        global n

        sheet.write(n, 0, item_title)
        sheet.write(n, 1, item_link)
        sheet.write(n, 2, item_dec)
        sheet.write(n, 3, item_view)
        sheet.write(n, 4, item_biubiu)
        sheet.write(n, 5, item_date)

        n = n + 1

关闭浏览器

最后,别忘了关闭浏览器:

finally:
    browser.close()

3. 使用 PhantomJS 无界面浏览器

除了 Chrome,PhantomJS 可以实现完全无界面的浏览体验。接下来我们将切换到 PhantomJS:

下载 PhantomJS

首先,下载 PhantomJS:下载地址。下载后需要配置环境变量。

使用 PhantomJS

将 Chrome 驱动替换为 PhantomJS:

browser = webdriver.PhantomJS()

这样就可以实现真正的无界面操作,不会打开任何浏览器界面!

以下是整理后的关于如何处理动态 JSON 数据的笔记,采用 Markdown 文档结构,并增强了趣味性:


06 如何处理动态 JSON 数据

在前面的内容中,我们已经学习了如何爬取静态的 HTML 数据。不过,许多常见的数据是动态的,例如:

  • 商品的评论数据
  • 实时直播的弹幕
  • 电影评分

这些数据会频繁变化,通常使用 JSON 格式进行传输。因为 JSON 非常轻量,以 key-value 的形式封装成对象,类似于 Python 中的字典。这次,我们以爬取「微信好友列表」为例,来看看如何玩转 JSON 数据。

1. Python 中的 JSON 模块

Python 提供了一个内置的 json 模块,用于处理 JSON 数据,主要有两个常用的函数:

  1. json.dumps(): 将 Python 对象转化为 JSON 字符串。
  2. json.loads(): 将 JSON 字符串转化为 Python 对象。

在爬取动态数据时,我们经常会使用 json.loads()

示例:解析微信好友 JSON 数据

假设我们获取到了以下微信好友的 JSON 数据:

import json

jsondata = '''
{
    "Uin": 0,
    "UserName": "@c482d142bc698bc3971d9f8c26335c5c",
    "NickName": "小帅b",
    "HeadImgUrl": "/cgi-bin/mmwebwx-bin/webwxgeticon?seq=500080&username=@c482d142bc698bc3971d9f8c26335c5c&skey=@crypt_b0f5e54e_b80a5e6dffebd14896dc9c72049712bf",
    "DisplayName": "",
    "ChatRoomId": 0,
    "KeyWord": "che",
    "EncryChatRoomId": "",
    "IsOwner": 0
}
'''

# 将 JSON 字符串转换为 Python 字典
myfriend = json.loads(jsondata)

现在,myfriend 已经被转化为一个 Python 字典对象。我们可以直接对其进行操作,例如获取昵称:

# 获取昵称
nickname = myfriend.get('NickName')
print(nickname)  # 输出: 小帅b

2. 处理 JSON 数组

除了 JSON 对象外,我们还经常遇到 JSON 数组。JSON 数组就是多个对象的组合,类似于 Python 中的列表(list)。

示例:解析微信好友列表 JSON 数据

以下是一个包含好友列表的 JSON 数组数据:

{
  "MemberList": [
    {
      "UserName": "小帅b",
      "sex": "男"
    },
    {
      "UserName": "小帅b的1号女朋友",
      "sex": "女"
    },
    {
      "UserName": "小帅b的2号女朋友",
      "sex": "女"
    }
  ]
}

假设我们将这个 JSON 数据存储为字符串 jsondata,我们可以将其转换为 Python 对象,并遍历其中的成员列表:

# 将 JSON 字符串转换为 Python 字典
myfriends = json.loads(jsondata)

# 获取好友列表
memberList = myfriends.get('MemberList')

# 遍历列表并打印每个好友的信息
for friend in memberList:
    username = friend.get('UserName')
    sex = friend.get('sex')
    print(f"用户名: {username}, 性别: {sex}")

输出结果:

用户名: 小帅b, 性别: 男
用户名: 小帅b的1号女朋友, 性别: 女
用户名: 小帅b的2号女朋友, 性别: 女

通过 json.loads(),我们将 JSON 数据转换为 Python 的对象(字典和列表),然后就可以使用 Python 的常用操作(如 .get()for 循环)对数据进行处理。

以下是整理后的关于 Python 多线程和线程池的笔记,采用 Markdown 文档结构,并增强了趣味性:


07 Python 多线程与线程池

在开发爬虫或者处理其他需要并发的任务时,我们经常需要使用多线程来提高效率。然而 Python 有一个叫做 GIL(Global Interpreter Lock) 的东西,这个锁用于控制线程的执行权限。它确保同一时间只有一个线程可以执行 Python 代码,从而避免线程安全问题。但这也导致了 Python 的多线程在 CPU 密集型任务上效率并不高。

1. 多进程与多线程的区别

为了充分利用多核 CPU:

  • 多进程multiprocessing):适用于 CPU 密集型任务。
  • 多线程threading):适用于 I/O 密集型任务(例如网络请求、文件读写)。

2. Python 常用的多线程模块

  • _thread
  • threading
  • Queue

thread 模块在 Python 3 中被弃用了,改名为 _thread,但通常使用 threading 就足够了,因为它提供了更高级别的线程管理。

3. 示例:让小明和小红一起摸鱼

我们通过创建线程来让小明和小红同时进行摸鱼的任务:

import threading
import time

# 创建一个线程子类
class MyThread(threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):
        print("开始线程:" + self.name)
        moyu_time(self.name, self.counter, 10)
        print("退出线程:" + self.name)

def moyu_time(threadName, delay, counter):
    while counter:
        time.sleep(delay)
        print("%s 开始摸鱼 %s" % (threadName, time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
        counter -= 1

# 创建新线程
thread1 = MyThread(1, "小明", 1)
thread2 = MyThread(2, "小红", 2)

# 开启新线程
thread1.start()
thread2.start()

# 等待至线程中止
thread1.join()
thread2.join()
print("退出主线程")

输出结果

开始线程:小明
开始线程:小红
小明 开始摸鱼 2024-09-04 23:15:26
小红 开始摸鱼 2024-09-04 23:15:27
...
退出线程:小明
退出线程:小红
退出主线程

通过以上代码,我们实现了小明和小红在各自的线程中进行摸鱼的任务。

4. 引入线程池

随着任务的增多,频繁的创建和销毁线程会浪费大量资源。为了提高效率,我们可以使用 线程池。线程池可以复用线程,从而减少资源浪费。Python 中可以使用 ThreadPoolExecutor 实现线程池。

示例:使用线程池进行摸鱼

from concurrent.futures import ThreadPoolExecutor
import time

def moyu_time(name, delay, counter):
    while counter:
        time.sleep(delay)
        print("%s 开始摸鱼 %s" % (name, time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))
        counter -= 1

if __name__ == '__main__':
    # 创建一个线程池,最多 20 个线程
    pool = ThreadPoolExecutor(20)
    for i in range(1, 5):
        pool.submit(moyu_time, 'xiaoshuaib' + str(i), 1, 3)

输出结果

xiaoshuaib1 开始摸鱼 2024-09-04 23:30:10
xiaoshuaib1 开始摸鱼 2024-09-04 23:30:11
...

5. 使用队列创建线程池

Queue 模块允许我们将任务放入队列,并由线程池中的线程取出执行。以下是使用队列创建线程池的示例:

示例:用队列创建线程池摸鱼

import threading
import time
from queue import Queue

class CustomThread(threading.Thread):
    def __init__(self, queue):
        threading.Thread.__init__(self)
        self.__queue = queue

    def run(self):
        while True:
            q_method = self.__queue.get()
            q_method()
            self.__queue.task_done()

def moyu():
    print("开始摸鱼 %s" % (time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())))

def queue_pool():
    queue = Queue(5)
    for i in range(queue.maxsize):
        t = CustomThread(queue)
        t.setDaemon(True)
        t.start()

    for i in range(20):
        queue.put(moyu)
    queue.join()

if __name__ == '__main__':
    queue_pool()

运行结果

开始摸鱼 2024-09-04 23:30:10
开始摸鱼 2024-09-04 23:30:11
...

通过队列实现线程池,我们可以更高效地管理线程,提高并发性能。

以下是整理后的关于 Python 多进程爬取豆瓣电影的笔记,采用 Markdown 文档结构,并增强了趣味性:


08 Python 多进程爬取豆瓣电影

在 Python 中,多线程的 GIL 锁限制了多线程在 CPU 密集型任务中的效率,特别是在多核 CPU 上,这让多线程在这种情况下显得有点“鸡肋”。为了解决这个问题,我们可以使用 多进程,这不仅可以避开 GIL 锁,还能实现真正的并行处理。

1. 使用多进程的基础

multiprocessing 模块是 Python 内置的多进程模块,使用它可以轻松实现多进程。以下是简单的使用示例:

示例 1:使用 Process 创建进程

from multiprocessing import Process

def f(name):
    print('hello', name)

if __name__ == '__main__':
    p = Process(target=f, args=('xiaoshuaib',))
    p.start()
    p.join()

在这个例子中,我们创建了一个新的进程 p,并执行函数 fstart() 方法启动进程,join() 方法等待进程执行完毕。

示例 2:使用进程池 Pool

进程池可以帮我们管理和复用多个进程,避免频繁创建和销毁进程所带来的性能开销。

from multiprocessing import Pool

def f(x):
    return x * x

if __name__ == '__main__':
    with Pool(5) as p:
        print(p.map(f, [1, 2, 3]))

在这个例子中,我们创建了一个包含 5 个进程的进程池,并使用 map() 方法将函数 f 应用于一个列表。

2. 用多进程爬取豆瓣电影

下面我们将使用多进程来爬取豆瓣电影的前 250 部影片,并将结果保存。以下是实现多进程爬取豆瓣电影的代码:

import multiprocessing
import time
from bs4 import BeautifulSoup
import requests

def request_douban(url):
    # 模拟请求豆瓣电影页面
    response = requests.get(url)
    return response.text

def save_content(soup):
    # 假设这里是保存电影数据的逻辑
    titles = soup.find_all('span', class_='title')
    for title in titles:
        print(title.get_text())

def main(url):
    html = request_douban(url)
    soup = BeautifulSoup(html, 'lxml')
    save_content(soup)

if __name__ == '__main__':
    start = time.time()
    urls = []
    pool = multiprocessing.Pool(multiprocessing.cpu_count())
    for i in range(0, 10):
        url = 'https://movie.douban.com/top250?start=' + str(i * 25) + '&filter='
        urls.append(url)
    pool.map(main, urls)
    pool.close()
    pool.join()
    print(f"爬取完成,用时:{time.time() - start:.2f} 秒")

代码解释

  1. 创建进程池:

    pool = multiprocessing.Pool(multiprocessing.cpu_count())
    

    根据电脑的 CPU 内核数量创建相应的进程池,确保不会创建过多的进程,避免不必要的资源浪费。

  2. 并行执行任务:

    pool.map(main, urls)
    

    使用 map() 方法将主函数 main 应用于 URL 列表 urls。每个进程会独立处理一个 URL,提升了爬取速度。

  3. 关闭和等待进程:

    • pool.close():关闭进程池,不再接受新的任务。
    • pool.join():等待所有进程执行完毕再结束。

运行效果

运行该代码后,可以看到多进程爬取显著提高了效率。运行时间取决于你的 CPU 核心数以及爬取的网页复杂度。

3. 对比和提升

将上述代码运行后,记录运行时间,并对比单线程或单进程爬取的速度,你会发现速度可能提升了好几倍。你还可以尝试爬取更多的数据,以更加明显地感受到多进程的效率优势。

提示

  • 如果你的 CPU 核心数较多,提升会更明显。
  • 对于 I/O 密集型任务,使用多线程可能也会有不错的效果,但对于 CPU 密集型任务,多进程是更好的选择。

快去试一下吧,看看你的爬虫效率有多大的提升!🚀

以下是关于使用 IP 代理池伪装 IP 地址防止被封的整理笔记,以 Markdown 文档结构呈现,并增强了趣味性:


09 使用 IP 代理池伪装你的 IP 地址

在进行爬虫时,我们可能会遇到反爬虫机制,被封禁 IP 是常有的事。为了解决这个问题,我们可以通过伪装自己的 IP 地址来避免被封,甚至还能通过 IP 代理池不断更换 IP 地址。

1. 伪装 IP 地址

Python 的 requests 库让我们很容易就可以使用代理 IP。下面是如何使用的示例:

示例:使用代理 IP

import requests

# 定义代理 IP
proxies = {
    'http': 'http://xx.xxx.xxx.xxx:xxxx',
    'https': 'http://xxx.xx.xx.xxx:xxx',
    # 可以添加多个代理 IP
}

# 使用代理发送请求
response = requests.get('https://example.com', proxies=proxies)
print(response.text)

获取代理 IP

免费的代理 IP 可以在网上轻松找到,但由于其不稳定,可能随时失效。如果你需要稳定的代理,可以考虑购买付费代理服务。

2. IP 代理池的介绍

为了高效管理和使用大量的代理 IP,我们可以使用 IP 代理池。IP 代理池通过程序抓取大量免费的代理 IP,并定期检测其可用性,以确保代理的质量和可用性。

现成的 IP 代理池

如果你不想自己搭建代理池,可以使用现成的开源项目。小帅b推荐一个开源的 IP 代理池:ProxyPool

如何使用 ProxyPool

  1. 克隆项目

    使用 git clone 将 ProxyPool 源代码拉到本地:

    git clone https://github.com/Python3WebSpider/ProxyPool.git
    
  2. 配置 Redis

    打开项目中的 setting.py 文件,配置 Redis 的地址和密码。你可以从 这里 下载 Redis。

  3. 安装所需模块

    在项目目录中安装相关 Python 模块:

    pip3 install -r requirements.txt
    
  4. 启动 Redis

    Redis 的默认端口是 6379,启动 Redis 服务:

    redis-server
    
  5. 运行代理池

    运行 run.py,启动代理池:

    python run.py
    

    运行成功后,代理池会开始抓取代理 IP 并存储到 Redis 数据库中。

  6. 测试代理池

    代理池运行后,你可以通过访问以下 URL 来随机获取一个代理 IP:

    http://localhost:5555/random
    

    如果访问成功,你会得到一个代理 IP。

使用代码获取代理 IP

以下是如何在代码中从代理池获取一个代理 IP 的示例:

import requests

PROXY_POOL_URL = 'http://localhost:5555/random'

def get_proxy():
    try:
        response = requests.get(PROXY_POOL_URL)
        if response.status_code == 200:
            return response.text
    except ConnectionError:
        return None

# 获取一个代理 IP
proxy = get_proxy()
print(f"使用的代理 IP: {proxy}")

# 使用获取的代理 IP 进行请求
proxies = {
    'http': proxy,
    'https': proxy,
}

response = requests.get('https://example.com', proxies=proxies)
print(response.text)

3. 解决常见问题

如果在运行过程中出现 AttributeError: 'int' object has no attribute 'items' 错误,可以尝试更新 Redis 版本:

pip3 install redis==2.10.6

总结

通过使用 IP 代理池,我们可以高效管理大量的代理 IP,避免被目标网站封禁。赶紧动手试试,为你的爬虫程序插上 IP 的翅膀,让它飞得更远吧!🚀

以下是关于处理需要登录的网站的三种方法的整理笔记,以 Markdown 文档结构呈现,并增强了趣味性:


10 遇到需要登录的网站怎么办?用这 3 招轻松搞定!

在进行数据爬取时,有时会遇到需要登录的网站。常见的登录方式主要有两种:

  1. 需要输入账号和密码登录。
  2. 需要输入账号、密码和验证码登录。

下面,我们先来解决第一种需要账号和密码的登录方式。

第一招:Cookie 大法

你有没有注意到,当你登录某个网站后,即使你关闭了浏览器或过了一段时间再访问,你依然是登录状态。这就是 Cookie 在起作用。

每个登录网站的用户,服务器都会为其分配一个 Cookie。下次请求数据时,只要将这个 Cookie 附带在请求中,服务器会识别你是登录用户,从而返回数据。Cookie 的有效期由服务器设定,过期后需要重新登录获取新的 Cookie。

  1. 获取 Cookie:通过登录一次网站,手动或使用工具获取登录后的 Cookie。

  2. 使用 Cookie:在请求数据时,将 Cookie 添加到请求头中。例如,使用 requests 库时,可以这样做:

    import requests
    
    # 定义请求头,包含 Cookie
    headers = {
        'User-Agent': '你的 User-Agent',
        'Cookie': 'your_cookie_string_here',
    }
    
    # 发送请求
    response = requests.get('https://example.com/protected_page', headers=headers)
    print(response.text)
    

第二招:Selenium 自动登录

Selenium 是一个强大的工具,可以模拟浏览器操作,包括自动登录。这种方法不仅可以应对简单的登录,还能处理复杂的 JavaScript 页面。

使用 Selenium 自动登录

  1. 安装 Selenium

    pip install selenium
    
  2. 登录操作

    使用 Selenium 自动填写账号、密码,并点击登录按钮。下面是示例代码:

    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    
    # 初始化 WebDriver
    driver = webdriver.Chrome()
    driver.get('https://example.com/login')
    
    # 等待元素加载并输入账号和密码
    WAIT = WebDriverWait(driver, 10)
    username = WAIT.until(EC.presence_of_element_located((By.CSS_SELECTOR, '账号的selector')))
    password = WAIT.until(EC.presence_of_element_located((By.CSS_SELECTOR, '密码的selector')))
    submit = WAIT.until(EC.element_to_be_clickable((By.XPATH, '按钮的xpath')))
    
    username.send_keys('你的账号')
    password.send_keys('你的密码')
    submit.click()
    
    # 获取登录后的 Cookie
    cookies = driver.get_cookies()
    print(cookies)
    
    # 使用获取到的 Cookie 请求数据
    driver.quit()
    
  3. 使用获取的 Cookie

    登录成功后,你可以将获取到的 Cookie 用于后续的请求,避免重复登录。

第三招:绕过验证码登录

有些网站登录时会要求输入验证码,这就需要进一步处理。以下是一些应对验证码的策略:

  1. 简单验证码:使用 OCR(光学字符识别)技术,可以尝试使用 Tesseract 等库来识别验证码。
  2. 复杂验证码:对于复杂的验证码(如滑动拼图、点击图形),可能需要使用打码平台或训练自定义模型来解决。

示例:使用 OCR 识别简单验证码

from PIL import Image
import pytesseract

# 读取验证码图片
image = Image.open('captcha.png')

# 使用 Tesseract 识别文字
captcha_text = pytesseract.image_to_string(image)
print(f'识别到的验证码: {captcha_text}')

使用打码平台

如果验证码非常复杂,可以考虑使用一些打码平台,如 2CaptchaRuCaptcha 等,这些平台通过人工识别验证码,返回验证码内容给程序。

以下是关于如何识别图片验证码的笔记,以 Markdown 文档结构呈现,并增强了趣味性:


11 如何识别图片验证码

在爬虫过程中,经常会遇到带有验证码的登录界面。图片验证码识别是个大问题,尤其是当图片中有噪点和颜色干扰时。为了提高验证码识别的正确率,我们需要对图片进行预处理,然后再使用 OCR 工具来识别。下面就一步步来讲解如何处理图片验证码。

1. 安装相关工具

首先,我们需要安装 Python 的 OCR 工具 Pytesseract,以及处理图片的库 PIL(Pillow)

安装 Pytesseract

pip install pytesseract

安装 Tesseract-OCR

  • Ubuntu 系统:可以使用以下命令安装 Tesseract-OCR

    sudo apt install tesseract-ocr
    
  • Windows 系统:请自行搜索并安装 Tesseract-OCR,并配置环境变量。

2. 导入模块

接着将相关模块导入到代码文件中:

try:
    from PIL import Image
except ImportError:
    import Image
import pytesseract

3. 简单识别验证码

首先,我们尝试用 Pytesseract 直接识别一张简单的验证码:

captcha = Image.open("captcha1.png")
result = pytesseract.image_to_string(captcha)
print(result)

然而,Pytesseract 无法处理太多噪点的图片。如果图片还带有彩色背景,识别难度更大。

4. 图片灰度处理

为了提高识别率,我们首先对图片进行灰度处理。

captcha = Image.open("captcha2.png")
result = captcha.convert('L')  # 灰度处理
result.show()

虽然已经灰度处理,但效果仍不理想。因此,我们需要进一步进行二值化处理。

5. 图片二值化

二值化是将图片转为只有黑白两色的图像,这样可以更好地去除噪点。以下是二值化的实现:

def convert_img(img, threshold):
    img = img.convert("L")  # 处理灰度
    pixels = img.load()
    for x in range(img.width):
        for y in range(img.height):
            if pixels[x, y] > threshold:
                pixels[x, y] = 255
            else:
                pixels[x, y] = 0
    return img

调用这个函数来处理图片:

captcha = Image.open("captcha2.png")
processed_img = convert_img(captcha, 150)
processed_img.show()

6. 使用 Pytesseract 识别处理后的图片

现在,我们对处理后的图片进行识别:

# 识别处理后的图片
result = pytesseract.image_to_string(processed_img)
print(result)

7. 处理带噪点的复杂验证码

当验证码中包含大量噪点时,需要进一步进行去噪处理。以下是去噪的代码示例:

def denoise_image(img):
    data = img.getdata()
    w, h = img.size
    for x in range(1, w-1):
        for y in range(1, h-1):
            # 获取各个像素方向
            mid_pixel = data[w * y + x]
            if mid_pixel == 0:
                top_pixel = data[w * (y - 1) + x]
                left_pixel = data[w * y + (x - 1)]
                down_pixel = data[w * (y + 1) + x]
                right_pixel = data[w * y + (x + 1)]
                count = 0
                if top_pixel == 0:
                    count += 1
                if left_pixel == 0:
                    count += 1
                if down_pixel == 0:
                    count += 1
                if right_pixel == 0:
                    count += 1
                # 如果黑色邻居少于 4 个,则认为是噪点
                if count < 1:
                    img.putpixel((x, y), 255)
    return img

使用这个去噪函数来处理复杂的验证码图像,再进行识别:

captcha = Image.open("captcha3.png")
clean_img = denoise_image(captcha)
result = pytesseract.image_to_string(clean_img)
print(result)

awesome-python-login-model

这个项目使用 python 模拟登录了各大型网站

下面是整理后的笔记,包含如何使用 CSV 文件保存数据、将数据存入 MySQL 数据库的步骤和示例代码:


12 数据保存方法:CSV 与 MySQL 数据库

在爬取数据后,需要将数据进行保存。这里介绍两种常见的保存方法:使用 CSV 文件和 MySQL 数据库。

1. 保存数据到 CSV 文件

为什么使用 CSV 文件?

  1. 大数据支持:CSV 文件能够保存大量数据。
  2. 便捷性:CSV 文件可以方便地导入到电子表格或数据库中。
  3. 语言支持广泛:几乎所有编程语言都可以处理 CSV 文件。

使用 Python 保存数据到 CSV 文件

import csv

with open('xiaoshuaib.csv', mode='w', newline='') as csv_file:
    fieldnames = ['你是谁', '你几岁', '你多高']
    writer = csv.DictWriter(csv_file, fieldnames=fieldnames)

    writer.writeheader()
    writer.writerow({'你是谁': '小帅b', '你几岁': '18岁', '你多高': '18cm'})
    writer.writerow({'你是谁': '小帅c', '你几岁': '19岁', '你多高': '17cm'})
    writer.writerow({'你是谁': '小帅d', '你几岁': '20岁', '你多高': '16cm'})

使用 Pandas 操作 CSV 文件

安装 Pandas

pip install pandas

读取 CSV 文件

import pandas as pd

xiaoshuaib = pd.read_csv('xiaoshuaib.csv')
print(xiaoshuaib)

保存数据到 CSV 文件

import pandas as pd

b = ['小帅b', '小帅c', '小帅d']
c = ['18岁', '19岁', '20岁']
d = ['18cm', '17cm', '16cm']

df = pd.DataFrame({'你是谁': b, '你几岁': c, '你多高': d})
df.to_csv("xsb.csv", index=False, sep=',')

2. 保存数据到 MySQL 数据库

连接 MySQL 数据库

安装 PyMySQL

pip install pymysql

创建数据库和表

import pymysql

# 连接数据库
db = pymysql.connect("localhost", "root", "password", "avIdol")

# 获取 cursor 操作数据库
cursor = db.cursor()

# 创建数据表
sql = """CREATE TABLE beautyGirls (
   name CHAR(20) NOT NULL,
   age INT)"""
cursor.execute(sql)

# 关闭数据库连接
db.close()

插入数据到 MySQL 表

import pymysql

# 连接数据库
db = pymysql.connect("localhost", "root", "password", "avIdol")

# 获取 cursor 操作数据库
cursor = db.cursor()

# 插入数据
sql = "INSERT INTO beautyGirls(name, age) VALUES ('Mrs.cang', 18)"

try:
    cursor.execute(sql)
    db.commit()  # 提交事务
except:
    db.rollback()  # 发生错误时回滚

# 关闭数据库连接
db.close()

更新和删除数据

删除数据示例

sql = "DELETE FROM beautyGirls WHERE age = '%d'" % (18)
try:
    cursor.execute(sql)
    db.commit()
except:
    db.rollback()

3. 将 CSV 文件内容插入到 MySQL 数据库

创建数据库和表

创建数据库

CREATE DATABASE xsb CHARACTER SET utf8 COLLATE utf8_general_ci;

创建表

CREATE TABLE xsb (name CHAR(20), age CHAR(20), length CHAR(20));

使用 Pandas 和 SQLAlchemy 将 CSV 插入 MySQL

安装 MySQL Client

pip install mysqlclient

插入 CSV 数据到 MySQL 表

import pandas as pd
from sqlalchemy import create_engine

# 读取 CSV 文件
df = pd.read_csv('xsb.csv')

# 连接数据库并插入数据
engine = create_engine('mysql://root@localhost/xsb?charset=utf8')
with engine.connect() as conn, conn.begin():
    df.to_sql('xsb', conn, if_exists='replace', index=False)

以上步骤完成后,检查数据库,确认数据已经成功插入到表中。

13 Python 数据可视化与 Scrapy 爬虫使用指南

1. 使用 Seaborn 进行数据可视化

Seaborn 是基于 Matplotlib 的高级数据可视化库,提供了更简单和直观的 API。以下是几种常见的图表示例。

1.1 散点图

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(style="darkgrid")

tips = sns.load_dataset("tips")
sns.relplot(x="total_bill", y="tip", data=tips)
plt.show()

1.2 折线图

fmri = sns.load_dataset("fmri")
sns.relplot(x="timepoint", y="signal", hue="event", kind="line", data=fmri)
plt.show()

1.3 直方图

titanic = sns.load_dataset("titanic")
sns.catplot(x="sex", y="survived", hue="class", kind="bar", data=titanic)
plt.show()

2. 使用 Pyecharts 进行数据可视化

Pyecharts 是基于百度 Echarts 的 Python 数据可视化库,特别适合与 Jupyter Notebook 结合使用,能创建丝滑的可视化效果。

2.1 直方图

from pyecharts.charts import Bar
from pyecharts import options as opts

bar = (
    Bar()
    .add_xaxis(["衬衫", "毛衣", "领带", "裤子", "风衣", "高跟鞋", "袜子"])
    .add_yaxis("商家A", [114, 55, 27, 101, 125, 27, 105])
    .add_yaxis("商家B", [57, 134, 137, 129, 145, 60, 49])
    .set_global_opts(title_opts=opts.TitleOpts(title="某商场销售情况"))
)
bar.render()

2.2 饼图

from pyecharts.charts import Pie
from pyecharts.faker import Faker

def pie_base() -> Pie:
    c = (
        Pie()
        .add("", [list(z) for z in zip(Faker.choose(), Faker.values())])
        .set_global_opts(title_opts=opts.TitleOpts(title="Pie-基本示例"))
        .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}"))
    )
    return c

# 需要安装 snapshot_selenium
from snapshot_selenium import make_snapshot
make_snapshot(driver, pie_base().render(), "pie.png")

2.3 词云图

from pyecharts.charts import WordCloud

words = [
    ("Sam S Club", 10000),
    ("Macys", 6181),
    ("Amy Schumer", 4386),
    ("Jurassic World", 4055),
    ("Charter Communications", 2467),
    ("Chick Fil A", 2244),
    ("Planet Fitness", 1868),
    ("Pitch Perfect", 1484),
    ("Express", 1112),
    ("Home", 865),
    ("Johnny Depp", 847),
    ("Lena Dunham", 582),
    ("Lewis Hamilton", 555),
    ("KXAN", 550),
    ("Mary Ellen Mark", 462),
    ("Farrah Abraham", 366),
    ("Rita Ora", 360),
    ("Serena Williams", 282),
    ("NCAA baseball tournament", 273),
    ("Point Break", 265),
]

def wordcloud_base() -> WordCloud:
    c = (
        WordCloud()
        .add("", words, word_size_range=[20, 100])
        .set_global_opts(title_opts=opts.TitleOpts(title="WordCloud-基本示例"))
    )
    return c

# 需要安装 snapshot_selenium
make_snapshot(driver, wordcloud_base().render(), "WordCloud.png")

3. 使用 Scrapy 爬取数据

Scrapy 是一个强大的 Python 爬虫框架,用于高效地抓取网络数据。

3.1 安装 Scrapy

可以使用以下命令安装 Scrapy:

  • 使用 Conda 安装

    conda install -c conda-forge scrapy
    
  • 使用 PyPI 安装

    pip install Scrapy
    

Scrapy 依赖的库包括 lxmlparselw3libtwistedcryptographypyOpenSSL

3.2 创建 Scrapy 爬虫项目

在终端中使用以下命令创建一个 Scrapy 爬虫项目,例如爬取糗事百科的段子:

scrapy startproject qiushibaike

3.3 编写 Scrapy 爬虫

spiders 目录下创建一个新的爬虫文件,如 qiushibaike_spider.py,并继承 scrapy.Spider 类。

import scrapy

class QiushiSpider(scrapy.Spider):
    name = "qiushibaike"

    def start_requests(self):
        urls = [
            'https://www.qiushibaike.com/text/page/1/',
            'https://www.qiushibaike.com/text/page/2/',
        ]
        for url in urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        page = response.url.split("/")[-2]
        filename = f'qiushi-{page}.html'
        with open(filename, 'wb') as f:
            f.write(response.body)
        self.log(f'Saved file {filename}')

3.4 运行爬虫

在项目目录下,使用以下命令运行爬虫:

cd qiushibaike/
scrapy crawl qiushibaike

3.5 爬虫反爬应对

如果爬虫被反爬机制阻止,可以在请求中添加请求头信息,例如添加 User-Agent:

headers = {
    'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/73.0.3683.86 Chrome/73.0.3683.86 Safari/537.36'
}

def start_requests(self):
    urls = [
        'https://www.qiushibaike.com/text/page/1/',
        'https://www.qiushibaike.com/text/page/2/',
    ]
    for url in urls:
        yield scrapy.Request(url=url, callback=self.parse, headers=headers)

3.6 数据解析与存储

parse 方法中,通过 XPath 或 CSS 选择器解析数据,并存储为本地文件或数据库。

解析段子的作者和内容

def parse(self, response):
    content_left_div = response.xpath('//*[@id="content-left"]')
    content_list_div = content_left_div.xpath('./div')

    for content_div in content_list_div:
        yield {
            'author': content_div.xpath('./div/a[2]/h2/text()').get(),
            'content': content_div.xpath('./a/div/span/text()').getall(),
        }

存储为 JSON 文件

使用以下命令将爬取的数据存储为 JSON 文件:

scrapy crawl qiushibaike -o qiushibaike.json

若出现中文乱码问题,在 settings.py 中添加:

FEED_EXPORT_ENCODING = 'utf-8'

3.7 使用 Scrapy 存储到 MongoDB

配置 Item

items.py 中定义要存储的数据字段:

import scrapy

class QiushibaikeItem(scrapy.Item):
    author = scrapy.Field()
    content = scrapy.Field()
    _id = scrapy.Field()

使用 Pipeline 存储数据

pipelines.py 中定义 MongoDB 的连接和存储逻辑:

import pymongo

class QiushibaikePipeline(object):

    def __init__(self):
        self.connection = pymongo.MongoClient('localhost', 27017)
        self.db = self.connection.scrapy
        self.collection = self.db.qiushibaike

    def process_item(self, item, spider):
        if not self.connection or not item:
            return
        self.collection.insert_one(dict(item))

    def __del__(self):
        if self.connection:
            self.connection.close()

配置 Pipelines

settings.py 中启用 Pipelines:

ITEM_PIPELINES = {
   'qiushibaike.pipelines.QiushibaikePipeline': 300,
}

运行爬虫

再次运行爬虫,并查看 MongoDB 中存储的数据:

scrapy crawl qiushibaike

通过以上步骤,你可以使用 Scrapy 抓取网页数据并存储到 MongoDB 中!

Not-By-AI