2023年7月30日星期日

py 爬虫

py 爬虫

简单爬虫架构

requests网页下载库

发送requests请求

reguests.get/post(url,params,data,headers,timeout,verify,allow redirects,cookies)

  • url:要下载的目标网页的URL
  • params:字典形式,设置URL后面的参数,比如?id=123&name=xiaoming
  • data:字典或者字符串,一般用于POST方法时提交数据
  • headers:设置user-agent、refer等请求头
  • timeout:超时时间,单位是秒
  • verify:True/False,是否进行HTTPS证书验证,默认是,需要自己设置证书地址
  • allow_redirects:True/False是否让requests做重定向处理,默认是
  • cookies:附带本地的cookies数据

接受response响应

requests.get/post(url)

  • r.status_code:查看状态码,如果等于200代表请求成功
  • r.encoding:可以查看当前编码,以及变更编码(重要!requests会根据Headers推测编码,推测不到则设置为ISO-8859-1可能导致乱码)
  • r.text查看返回的网页内容
  • r.headers:查看返回的HTTP的headers
  • r.url:查看实际访问的URL
  • r.content:以字节的方式返回内容,比如用于下载图片
  • r.cookies:服务端要写入本地的cookies数据

注意事项

如果headers没有encoding信息,默认ISO-8859-1
中文网页可能出现乱码,查看text,在<header>标签中<meta \>charset成员可能含有编码信息
可自行设定r.encoding成员

URL管理器

作用:对爬取URL进行管理,防止重复和循环爬取,支持新增URL和取出URL
URL-manager.png
utils/url_manager.py

class UrlManager:
    """
    URL管理器
    """

    def __init__(self) -> None:
        self.newUrls = set()
        self.oldUrls = set()
        pass

    def AddUrl(self, url):
        if url is None or len(url) == 0:
            return  # 不合格
        # 判断url是否已经存在
        if url in self.newUrls or url in self.oldUrls:
            return
        self.newUrls.add(url)

    def AddUrls(self, urls):
        if urls is None or len(urls) == 0:
            return
        for url in urls:
            self.AddUrl(url)
        pass

    def GetUrl(self):
        if self.HasNewUrl():
            url = self.newUrls.pop()
            self.oldUrls.add(url)
            return url
        else:
            return None

    def HasNewUrl(self):
        return len(self.newUrls) > 0

HTML简介

html.png

网页解析器 beautiful soup

语法
bs4.png
myBS.py
BeautifulSoup方法选择器find()方的使用

关于find()函数参数的一些tips

  1. 可以抓取存在某种标签的情形和存在标签匹配的情形
    items = soup.find_all("div","class")
    
  2. 可以部分匹配
    <div>标签们中有两个数据<div class="craw_me marisa"><div class="craw_me reimu">
    执行
    items = soup.find_all("div", class_="craw_me")
    print(items)
    
    后,输出列表[<div class="craw_me reimu">you craw reimu </div>, <div class="craw_me marisa">you craw marisa </div>]。即find()可以部分匹配

beautifulsoup获取有价值的信息

  1. view-source,未加载动态之前的源代码
  2. 右键-检查
    Elements->浏览器展示真实看到的代码,如已经加载js
    Network抓包
    • preserve log 跳转到新页面时保存原来log
    • disable cache 防止从本地取数据

Doc: Headers的一些重要参数

Request Method Get/Post
Status Code 200请求成功
Content-Type 返回,可能是text/html 可能是json等。以及有编码如UTF-8
Cookie
User-Agent

实战

爬取静态网站

爬取epicmo的博文标题
blogtest.py

爬去文章博客全部文章列表

craw.png

requests请求时附带cookie字典

import requests
cookies = {
    "captchakey":"14a54079a1",
    "captchaExpire":"1548852352"
}
r = requests.get(url, cookies=cookies)

正则表达式实现模糊匹配

r/R:非转义的原始字符串

与普通字符相比,其他相对特殊的字符,其中可能包含转义字符,即那些,反斜杠加上对应字母,表示对应的特殊含义的,比如最常见的”\n”表示换行,”\t”表示Tab等。而如果是以r开头,那么说明后面的字符,都是普通的字符了,即如果是“\n”那么表示一个反斜杠字符,一个字母n,而不是表示换行了。
以r开头的字符,常用于正则表达式,对应着re模块。

版权声明:本文为CSDN博主「抖腿大刘」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u010496169/article/details/70045895

url1 ="http://www.crazyant.net/123.html"
url2 ="http://www.crazyant.net/123.html#comments
url3 ="http://www.baidu.com'
import re
pattern = r'^http://www.crazyant.net/\d+\.html$'
print(re.match(pattern, url1)) # ok
print(re.match(pattern,url2)) # None
print(re.match(pattern,url3)) # None 

一个有很多爬虫实例的博客

filmreview

从url https://movie.douban.com/review/best/ 中获取热门评论共5页,包括评价电影,时间,文本内容。并将结果存在数据库中。
在这个案例中着重使用了user-agent简单破解网站的反爬机制,以及使用多个find来定位所需要的数据。以及一些sql的小知识。

北京天气

https://tianqi.2345.com/wea_history/54511.htm
涉及技术

  • headers中设置user agent反爬机制
  • 通过network抓包,payload中有所传参数,分析ajax的请求和参数
  • 通过for循环请求不同的参数的数据
  • 利用pandas实现excel的合并与保存

[pre]beautifulsoup抓取动态网页

(https://blog.51cto.com/u_15057841/3460666)

selenium实现关于动态网页的抓取

前提,selenium教程
本文过时信息注意
python3.0以后 selenuim.webdriver 库不在推荐使用find_element_by_接后缀的方法
现在使用find_element和find_elements方法,其中

  1. 通过webdriver对象的find_element(“属性名”,“属性值”)

    • 我们要定位一个属性id,值为"wang"的元素
    find_element('id','wang')
    
    • 我们要定位一个属性class,值为"plant"的元素
    find_element('id',"plant")
    
  2. 通过webdriver模块中的By,以指定方式定位元
    导入模块:from selenium.webdriver.common.by import By
    如:定位id为username,class_name为password,tag_name为input的元素

    from selenium.webdriver.common.by import By
    webdriver.find_element(By.ID,'username')
    webdriver.find_element(By.CLASS_NAME,'password')
    webdriver.find_element(By.TAG_NAME,'input')
    

    其实这种方法和第一种方法类似,因为By类中的ID,CLASS_NAME这些成员都是常量字符串,值就是’id’,‘class name’

  3. find_element()find_elements()的区别

  • find_element()的返回结果是一个WebElement对象,如果符合条件的有多个,默认返回找到的第一个,如果没有找到则抛出NoSuchElementException异常。
  • find_elements()的返回结果是一个包含所有符合条件的WebElement对象的列表,如果未找到,则返回一个空列表。

如果要做到像soup那样的键值对部分匹配,需要用到xpath定位

xpath常用表达式

表达式 描述 举例
nodename 选取此节点的所有子节点 form,选取form节点
/ 从根节点开始选取,绝对定位 /html/body/form/input,选取input节点
// 从符合条件的元素的开始,而不考虑它们的位置。相对定位 //form/input,选取input节点
form//input,选择form元素的后代的所有 input 元素
. 选取当前节点
选取当前节点的父节点 //input/..
@ 元素属性 //form/input[@name],选取带有name属性的input节点;
//input[@name=‘username’] ,选取所有name属性为username的input节点;
//input[@*]选择有任何属性的input节点
[] 如果有多个元素,可以进行筛选 /div[1]第一个(和传统排序方法不同);
/div[last()]最后一个;
/div[last()-1]倒数第二个
* 选择任何节点 /form/*,选择form之后的所有节点
| 或者 //form | //a,选择所有的input和a节点
@属性名 选取属性 //a[@id=“link1”]/@href,选取id为link1的a标签的href属性值(此方法在lxml可用)
text() 获取文本内容 //a[@id=“link1”]/text(),选取id为link1的a标签的文本内容(此方法在lxml可用)
contains() 包含内容(模糊匹配) //span[contains(text(), “Latest”)],选取文本内容包含“Latest”的span标签

css常用表达式

表达式说明举例
#id通过id选择元素#username,选择id为username的元素
.class通过类选择元素.container ,选择class为container的元素
element通过元素名选择元素input,选择所有input元素
[attribute]通过属性选择元素,选择具有attribute属性的元素
[attribute=value]通过属性选择元素[type=“password”],选择type属性为password的元素
a:link,a:visited选择未被访问、已被访问过的元素
p:empty选择没有子元素的p元素
element>element选择父元素为 div 的所有input 元素div>input

表达式实例

test.html举例,

# 绝对路径(层级关系)定位
# soup.find_all(xxx)[n].find(xxx)
data = edge.find_elements("xpath","/html/body/div")
data = edge.find_elements("xpath","/html/body/div[1]")
data = edge.find_elements(By.CSS_SELECTOR, "html>body>div") # 这种方法只能返回第一或者一个列表而不能div[2]这种返回指定序号

执行命令,可以得到一个列表,而在列表[0]又可以find_element
此外有意思的是

[0].text
‘段落\n百度\n疯狂的蚂蚁\n爱奇艺’

# 利用元素属性定位
# soup.find_all(class_=["xxx",...])
data = edge.find_elements(By.XPATH,"//div") # 返回列表
# 层级+元素属性定位
# soup.find_all("div",class_=["xxx",...])
data = edge.find_elements(By.XPATH,"//div[@nb='233']") # 完全匹配,返回列表,两个元素
data = edge.find_elements(By.XPATH, "//div[@class='craw_me reimu']")  # 完全匹配,唯一
data = edge.find_elements(By.XPATH, "//div[contains(@class,'craw_me')]")  # 部分包含

data = edge.find_elements(By.CSS_SELECTOR, "html>body>div[nb='233']") # 完全匹配,返回列表,两个元素
# 逻辑运算符定位
data = edge.find_elements(By.XPATH, "//div[contains(@class,'craw_me') and contains(@nb,'233')]")  

表达式定位总结

XPath常用的定位方式

  1. 元素属性,快速定位,唯一属性:
    //*[@id="images"]

  2. 层级与属性结合,解决没有属性问题:
    //div[@id="images"]/a[1]

  3. 属性与逻辑结合,解决多个属性重名问题:
    //*[@id="su" and @class="bg s_btn" ]

注意,表达式里的下标是从1开始的。
绝对定位以/开头,依赖页面的元素的顺序和位置,相对定位以//开头,不依赖页面元素顺序和位置,根据条件进行匹配,优先使用相对定位。
学习XPath本质就是掌握各种表达式的技巧,除了上述说到方法外,还有一些特别的定位方式:

  1. 查找id属性的值包含"kw"的元素:
    //*[contains(@id,'kw')]

  2. 查找⽂本⾥包含"新闻"的元素:
    //*[contains(text(),'新闻')]

  3. 查找class属性中开始位置包含’s_form_wrapper’关键字的元素:
    //*[starts-with(@class,'s_form_wrapper')]

  4. 使⽤多个相对路径去定位⼀个元素⽤//分开:
    //div[@class=‘formgroup’]//input[@id=‘user-message’]

  5. 轴定位:
    轴定位,使用::表示
    https://zhuanlan.zhihu.com/p/342903085
    查找id="head"元素后⾯标签名为input的第一个元素
    https://zhuanlan.zhihu.com/p/342903085
    //*[@id="head"]//following::input[1]

其次link定位和partial-link定位对于匹配text的效果也非常好,这里截取原blog相关内容
link 专门用来定位文本链接,假如要定位下面这一标签。
<div class="practice-box" data-v-04f46969="">加入!每日一练</div>
我们使用find_element_by_link_text 并指明标签内全部文本即可定位。
driver.find_element_by_link_text("加入!每日一练")

partial_link 翻译过来就是“部分链接”,对于有些文本很长,这时候就可以只指定部分文本即可定位,同样使用刚才的例子。
<div class="practice-box" data-v-04f46969="">加入!每日一练</div>
我们使用 find_element_by_partial_link_text 并指明标签内部分文本进行定位。
driver.find_element_by_partial_link_text("加入")
当然由于方法的过期,以上方法改为find_element(By.LINK_TEXT,“xxx”)

selenium搭配beautifulsoup

由于selenium的find方法不太熟悉,这里搭配beautifulsoup
selenium搭配beautifulsoup
版权声明:本文为CSDN博主「Java Punk」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

  1. 导入关键库

    import time                             # time函数
    import xlwt                             # 进行excel操作
    import os.path                          # os读写
    from bs4 import BeautifulSoup           # 解析html的
    from selenium import webdriver          # selenium 驱动
    from selenium.webdriver.common.by import By
    from selenium.webdriver.common.keys import Keys                     # 模仿键盘
    from selenium.webdriver.support.wait import WebDriverWait           # 导入等待类
    from selenium.webdriver.support import expected_conditions as EC    # 等待条件
    
  2. Selenium 解析动态Html
    原博客中采用了

    判断当前页面没有明显的元素用来判断拉到什么位置就是底部,所以我的规则是:一直循环,直到连续5次Keys.PAGE_DOWN(下拉),<li> 标签数量不再增加,就认为已经到底了。为了避免程序计算太快,每次下拉还停顿了0.2秒,实际效果不错。

  3. Selenium 转 BeautifulSoup

    根据小编的开发经验,selenium 很擅长模拟和测试,它动态加载的特性是 BeautifulSoup不 具备的。但是,对于取值操作,简单的还好,复杂点的比如:循环

  4. 标签这种操作,我还是觉得BeautifulSoup更方便。
    在爬虫的世界里,大量有价值的数据都是循环展现的,比如:某排行榜,某商品列表等…所以,Selenium + BeautifulSoup的操作必不可少。
    核心代码也非常简单,直接传入 Selenium 驱动 driver,用 page_source() 就可以啦。
  5. # 获取完整渲染的网页源代码
    pageSource = driver.page_source
    soup = BeautifulSoup(pageSource, 'html.parser')
    soup.prettify()
    
  6. 保存数据至excel

     def saveData(datalist, savepath):
         print("—————————— save ——————————")
         book = xlwt.Workbook(encoding="utf-8",style_compression=0)      # 创建workbook对象
         sheet = book.add_sheet(dataTime, cell_overwrite_ok=True)        # 创建工作表。sheet页名为dataTime
         col = ("排名", "图片链接", "名称", "品牌", "指导价", "销量")
         for i in range(0, len(col)):
             sheet.write(0, i, col[i])           # 列名
         for i in range(0, len(datalist)):
             # print("第%d条" %(i+1))             # 输出语句,用来测试
             data = datalist[i]
             for j in range(0, len(col)):
                 sheet.write(i+1, j, data[j])    # 数据
         if os.path.exists(savepath):            # 清空路径
             os.remove(savepath)
         book.save(savepath)                     # 保存
    

另外,在做爬虫时,通常是不需要打开浏览器的,只需要使用浏览器的内核,因此可以使用Chrome的无头模式

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
option = webdriver.EdgeOptions()
option.add_experimental_option("detach", True)
option.add_argument("--headless")

浏览器窗口切换

作者:Dream丶Killer
链接:https://juejin.cn/post/7074779332819812389
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

在很多时候我们都需要用到窗口切换,比如:当我们点击注册按钮时,它一般会打开一个新的标签页,但实际上代码并没有切换到最新页面中,这时你如果要定位注册页面的标签就会发现定位不到,这时就需要将实际窗口切换到最新打开的那个窗口。我们先获取当前各个窗口的句柄,这些信息的保存顺序是按照时间来的,最新打开的窗口放在数组的末尾,这时我们就可以定位到最新打开的那个窗口了。

# 获取打开的多个窗口句柄
windows = driver.window_handles
# 切换到当前最新打开的窗口
driver.switch_to.window(windows[-1])
方法描述
send_keys()模拟输入指定内容
clear()清除文本内容
is_displayed()判断该元素是否可见
get_attribute()获取标签属性值
size返回元素的尺寸
text返回元素文本

鼠标和键盘

作者:Dream丶Killer
链接:https://juejin.cn/post/7074779332819812389
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
在webdriver 中,鼠标操作都封装在ActionChains类中,常见方法如下:

方法描述
click()单击左键
context_click()单击右键
double_click()双击
drag_and_drop()拖动
move_to_element()鼠标悬停
perform()执行所有ActionChains中存储的动作

单击左键

模拟完成单击鼠标左键的操作,一般点击进入子页面等会用到,左键不需要用到 ActionChains 。

# 定位搜索按钮
button = driver.find_element_by_xpath('//*[@id="toolbar-search-button"]/span')
# 执行单击操作
button.click()

单击右键

鼠标右击的操作与左击有很大不同,需要使用 ActionChains 。

from selenium.webdriver.common.action_chains import ActionChains
# 定位搜索按钮
button = driver.find_element_by_xpath('//*[@id="toolbar-search-button"]/span')
# 右键搜索按钮
ActionChains(driver).context_click(button).perform()

双击

模拟鼠标双击操作。

# 定位搜索按钮
button = driver.find_element_by_xpath('//*[@id="toolbar-search-button"]/span')
# 执行双击动作
ActionChains(driver).double_click(button).perform()

拖动

模拟鼠标拖动操作,该操作有两个必要参数,

  • source:鼠标拖动的元素
  • target:鼠标拖至并释放的目标元素
# 定位要拖动的元素
source = driver.find_element_by_xpath('xxx')
# 定位目标元素
target = driver.find_element_by_xpath('xxx')
# 执行拖动动作
ActionChains(driver).drag_and_drop(source, target).perform()

鼠标悬停

模拟悬停的作用一般是为了显示隐藏的下拉框,比如 CSDN 主页的收藏栏,我们看一下效果。
csdn网站鼠标悬浮在右上方的收藏栏按钮,显示了隐藏的收藏栏

# 定位收藏栏
collect  = driver.find_element_by_xpath('//*[@id="csdn-toolbar"]/div/div/div[3]/div/div[3]/a')
# 悬停至收藏标签处
ActionChains(driver).move_to_element(collect).perform()

键盘控制

webdriver 中 Keys 类几乎提供了键盘上的所有按键方法,我们可以使用 send_keys + Keys 实现输出键盘上的组合按键如 “Ctrl + C”、“Ctrl + V” 等。

from selenium.webdriver.common.keys import Keys

# 定位输入框并输入文本
driver.find_element_by_id('xxx').send_keys('Dream丶killer')

# 模拟回车键进行跳转(输入内容后) 
driver.find_element_by_id('xxx').send_keys(Keys.ENTER)

# 使用 Backspace 来删除一个字符
driver.find_element_by_id('xxx').send_keys(Keys.BACK_SPACE)

# Ctrl + A 全选输入框中内容
driver.find_element_by_id('xxx').send_keys(Keys.CONTROL, 'a')

# Ctrl + C 复制输入框中内容
driver.find_element_by_id('xxx').send_keys(Keys.CONTROL, 'c')

# Ctrl + V 粘贴输入框中内容
driver.find_element_by_id('xxx').send_keys(Keys.CONTROL, 'v')

其他常见键盘操作:

操作描述
Keys.F1F1键
Keys.SPACE空格
Keys.TABTab键
Keys.ESCAPEESC键
Keys.ALTAlt键
Keys.SHIFTShift键
Keys.ARROW_DOWN向下箭头
Keys.ARROW_LEFT向左箭头
Keys.ARROW_RIGHT向右箭头
Keys.ARROW_UP向上箭头

[pre]元素等待

[pre]弹窗处理

cookies操作

作者:Dream丶Killer
链接:https://juejin.cn/post/7074779332819812389
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

cookies 是识别用户登录与否的关键,爬虫中常常使用 selenium + requests 实现 cookie 持久化,即先用 selenium 模拟登陆获取 cookie ,再通过 requests 携带 cookie 进行请求。
webdriver 提供 cookies 的几种操作:读取、添加删除。

  • get_cookies:以字典的形式返回当前会话中可见的 cookie 信息。
  • get_cookie(name):返回 cookie 字典中 key == name 的 cookie 信息。
  • add_cookie(cookie_dict):将 cookie 添加到当前会话中
  • delete_cookie(name):删除指定名称的单个 cookie。
  • delete_all_cookies():删除会话范围内的所有 cookie。

下面看一下简单的示例,演示了它们的用法。

from selenium import webdriver 

driver = webdriver.Chrome()
driver.get("https://blog.csdn.net/")

# 输出所有cookie信息
print(driver.get_cookies())

cookie_dict = {
    'domain': '.csdn.net',
    'expiry': 1664765502,
    'httpOnly': False,
    'name': 'test',
    'path': '/',
    'secure': True,
    'value': 'null'
}

# 添加cookie
driver.add_cookie(cookie_dict)
# 显示 name = 'test' 的cookie信息
print(driver.get_cookie('test'))
# 删除 name = 'test' 的cookie信息
driver.delete_cookie('test')
# 删除当前会话中的所有cookie
driver.delete_all_cookies()

其他参考教程
Python爬虫库xPath, BeautifulSoup, re, selenium的详细用法

实战:爬取pixiv某画师的所有图片

涉及技术:反爬,使用第三方反向代理,二进制读取,二进制存储
Python爬虫之如何玩转cookie(技巧篇)

1 条评论: