爬虫学习(三)

正文共: 12842字 6图 预计阅读时间: 33分钟

每日分享

When something is important enough, you do it even if the odds are not in your favor.

当一件事情足够重要,即使胜利的天平不在你这边,你也必须迎头而上。

小闫语录

我们总要为自己去拼一次,不论结果,不论其他。

爬虫学习(三)

1. XPATH

什么是XPATH?

XPath是一门在HTML/XML文档中查找信息的语言,可用来在HTML/XML文档中对元素和属性进行遍历。

节点:每个XML的标签我们都称之为节点。

1.1 基础语法

XPath使用路径表达式来选取XML文档中的节点或者节点集。这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。

nodename:选取此节点的所有子节点。

/:从根节点选取。

//:从匹配选择的当前节点,选择文档中的节点,而不考虑他们的位置。

.:选取当前节点。

..:选取当前节点的父节点。

@:选取属性。

使用Chrome插件选择标签的时候,选中时,选中的标签会添加属性class="xh-highlight"

1.1.1查找某个特定的节点或者包含某个指定的值的节点

选取属于bookstore子元素的第一个book元素:

/bookstore/book[1]

选取属于bookstore子元素的最后一个book元素:

/bookstore/book[last()]

选取bookstore子元素的倒数第二个book元素:

/bookstore/book[last()-1]

选取最前面的两个属于bookstore元素的子元素的book元素:

/bookstore/book[position()<3]

选取所有拥有名为lang的属性的title元素:

//title[@lang]

选取所有title元素,且这些元素拥有值为eng的lang属性:

//title[@lang='eng']

选取bookstore元素的所有book元素,且其中的price元素的值必须大于35.00:

/bookstore/book[price>35.00]

选取bookstore元素中的book元素的所有title元素,且其中的price元素的值必须大于35.00:

/bookstore/book[price>35.00]/title

找到包含下一页这三个字的文本:

//*[contains(text(),'下一页')]

1.1.2选取未知节点

.:匹配任何元素节点。

@*:匹配任何属性节点。

node():匹配任何类型的节点。

举例

选取bookstore元素的所有子元素:

/bookstore/*

选取文档中的所有元素:

//*

选取html下面任意节点下的meta节点的所有属性:

html/node()/meta/@*

选取所有带有属性的title元素:

//title[@*]

1.1.3注意点

  • 找字符串的时候(标签中的文本),一般在路径后面加上 text()。
  • 找链接的时候可以使用 link,如果有多个链接的时候可以使用 link[1]这样来选取。
  • 找type属性时可以使用 @type。
  • 相对路径 //,使用时需要先选取指定元素,然后再使用。
  • 如果要查找的标签没有特殊属性,我们可以定位到它的上一级查找。三级标签之内肯定会有属性。
  • position()可以使用比较运算(大于小于等于), last()只可以用来算术运算(加减)。
  • 我们可以同时查询两个语句,用 |隔开,得到两个结果。
  • 我们选择元素,右键使用copy XPath的时候,可能此语句在后端代码中无法执行(无法查找到指定的元素),这时就需要使用XPath语法对其进行修改,这也就是为什么有这么方便的工具我们仍然要学习语法。

2. lxml库

安装

pip3 install lxml

导入lxml的etree库

form lxml import etree

利用etree.HTML,将html字符串转化为Element对象

html = etree.HTML(response.content)

Element对象具有XPath的方法

html.xpath(xpath语句)

使用etree.tostring(html)将element对象转换成html文档。

html.xpath()获取的是一个列表,查询里面的内容需要使用索引。 lxml可以自动修正html代码。

xpath方法返回列表的三种情况

1.返回空列表:根据xpath语法规则字符串,没有定位到任何元素。

2.返回由字符串构成的列表:xpath字符串规则匹配的一定是文本内容或某属性的值。

3.返回由Element对象构成的列表:xpath规则字符串匹配的是标签,列表中的Element对象可以继续进行xpath。

lxml库的使用步骤

1.实例化etree对象,必须接受响应数据

2.通过etree对象,可以调用xpath()函数,使用XPath语句。

from lxml import etree
text = ''' <div> <ul> 
        <li class="item-1"><a href="link1.html">first item</a></li> 
        <li class="item-1"><a href="link2.html">second item</a></li> 
        <li class="item-inactive"><a href="link3.html">third item</a></li> 
        <li class="item-1"><a href="link4.html">fourth item</a></li> 
        <li class="item-0"><a href="link5.html">fifth item</a> 
        </ul> </div> '''

html = etree.HTML(text)
# 查询节点列表
node_list = html.xpath("//div/ul/li")
# 遍历节点列表,查询a标签的内容
for node in node_list:
    # 如果标签中没有值需要进行判断。
    a_text = node.xpath('./a/text()')[0] if node.xpath('./a/text()') else None
    a_href = node.xpath('./a/@href')[0]
    print(a_text)
    print(a_href)

2.1 案例

需求:爬取百度贴吧所有帖子图片(美女吧)

此处将贴吧置顶的帖子以及一些广告帖子给过滤掉,只有每页上的正常帖子。

步骤

1.构建请求信息。

2.发送请求,获取响应。

3.解析响应数据,返回贴吧列表链接、下一页链接。

4.遍历贴吧列表链接,解析每个帖子的图片列表链接,返回图片链接。

5.遍历图片链接,发送请求,下载图片,保存图片。

6.翻页操作。

爬取百度贴吧的时候,发现他的数据藏在了HTML页面的注释中,是根据js解析出来的。如果遇到诸如此类的网站,数据是根据js修改后加载的。我们只需要提供一个不支持js的浏览器版本即可。换一个古老的用户代理。

案例实现

import os
from lxml import etree
import requests

# 贴吧列表: //li[@class=" j_thread_list clearfix"]/div/div[2]/div[1]/div[1]/a
# 下一页链接://*[@id="frs_list_pager"]/a[contains(text(),'下一页')]/@href
# 贴吧图片链接://*[contains(@id,"post_content")]/img/@src

class Tieba:

    def __init__(self):
        self.url = 'https://tieba.baidu.com/f?kw=%E7%BE%8E%E5%A5%B3&ie=utf-8&pn=0'
        self.hearders = {
            # 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.96 Safari/537.36'
            'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0'
        }
    def get_data(self,url):
        resp = requests.get(url,headers=self.hearders)
        return resp.content

    def parse_data(self,detail_data):
        html = etree.HTML(detail_data)
        data_list = []
        node_list = html.xpath('//li[@class=" j_thread_list clearfix"]/div/div[2]/div[1]/div[1]/a')
        for node in node_list:
            temp = dict()
            temp['url'] = 'https://tieba.baidu.com' + node.xpath('./@href')[0]
            data_list.append(temp)
        next_url = html.xpath("""//*[@id="frs_list_pager"]/a[contains(text(),'下一页')]/@href""")
        return  data_list,next_url

    def parse_detail_data(self,data):
        html = etree.HTML(data)
        # 解析图片列表
        image_list = html.xpath('//*[contains(@id,"post_content")]/img/@src')
        return image_list

    def save_image(self,image_list):
        # 保存文件文件到文件夹,自动指定
        if not os.path.exists('images'):
            os.mkdir('images')
        for url in image_list:
            # 下载图片之前,过滤掉表情图片
            if '.jpg' not in url:
                continue
            print(url)
            resp = self.get_data(url)
            file_name = 'images' + os.sep +url.split('/')[-1]
            # 保存文件
            with open(file_name,'wb') as f:
                f.write(resp)

    def run(self):
        url = self.url
        while True:
            data = self.get_data(url)
            data_list,next_url = self.parse_data(data)
            for det in data_list:
                detail_data = self.get_data(det['url'])
                image_list = self.parse_detail_data(detail_data)
                self.save_image(image_list)
            if next_url == []:
                break
            else:
                url = 'https:' + next_url[0]

if __name__ == '__main__':
    tieba = Tieba()
    tieba.run()

保存文件:os.sep可以跨平台,实现区分文件路径的斜线

重点

1.XPath的使用!!

2.更换古老版本的User-Agent,以避免js对数据的影响。

3. josnpath

当字典非常的复杂时候,数据嵌套层数比较多,我们可以使用 jsonpath。需要注意的是:根节点是 $

使用的很少。

使用举例

book_dict = { 
  "store": {
    "book": [ 
      { "category": "reference",
        "author": "Nigel Rees",
        "title": "Sayings of the Century",
        "price": 8.95
      },
      { "category": "fiction",
        "author": "Evelyn Waugh",
        "title": "Sword of Honour",
        "price": 12.99
      },
      { "category": "fiction",
        "author": "Herman Melville",
        "title": "Moby Dick",
        "isbn": "0-553-21311-3",
        "price": 8.99
      },
      { "category": "fiction",
        "author": "J. R. R. Tolkien",
        "title": "The Lord of the Rings",
        "isbn": "0-395-19395-8",
        "price": 22.99
      }
    ],
    "bicycle": {
      "color": "red",
      "price": 19.95
    }
  }
}

from jsonpath import jsonpath

print(jsonpath(book_dict, '$..author')) # 返回列表,如果取不到将返回False

4. Selenium

可以驱动电脑中的浏览器,百分百还原浏览器操作,只不过是自动的。它相当于爬虫工程师手中的一张底牌,终极武器。如果我们尝试了各种手段后,任然爬取不到数据,就可以使用selenium,如果还是爬取不到,那么就真的获取不到了。

selenium(硒) ,可以简单的理解为一个测试工具,但其功能不止如此。

4.1爬虫、反爬与反反爬

4.1.1 明确反反爬的主要思路

反反爬的主要思路就是:尽可能的去模拟浏览器,浏览器在如何操作,代码中就如何去实现

例如:浏览器先请求了地址url1,保留了cookie在本地,之后请求地址url2,带上了之前的cookie,代码中也可以这样去实现。

4.1.2 通过headers字段来反爬

headers中有很多字段,这些字段都有可能会被对方服务器拿过来进行判断是否为爬虫。

4.1.2.1 通过headers中的User-Agent字段来反爬

反爬原理:爬虫默认情况下没有User-Agent。

解决方法:请求之前添加User-Agent即可;更好的方式是使用User-Agent池来解决(收集一堆User-Agent的方式,或者是随机生成User-Agent)。

import random

def get_ua():
    first_num = random.randint(55, 62)
    third_num = random.randint(0, 3200)
    fourth_num = random.randint(0, 140)
    os_type = [
        '(Windows NT 6.1; WOW64)', '(Windows NT 10.0; WOW64)', '(X11; Linux x86_64)',
        '(Macintosh; Intel Mac OS X 10_12_6)'
    ]
    chrome_version = 'Chrome/{}.0.{}.{}'.format(first_num, third_num, fourth_num)

    ua = ' '.join(['Mozilla/5.0', random.choice(os_type), 'AppleWebKit/537.36',
                   '(KHTML, like Gecko)', chrome_version, 'Safari/537.36']
                  )
    return ua
4.1.2.2 通过referer字段或者是其他字段来反爬

反爬原理:爬虫默认情况下不会带上referer字段。

解决方法:添加referer字段(例如豆瓣美剧爬虫)。

4.1.2.3 通过cookie来反爬

如果目标网站不需要登录:每次请求带上前一次返回的cookie,比如requests模块的session。

如果目标网站需要登录:准备多个账号,通过一个程序获取账号对应的cookie,组成cookie池,其他程序使用这些cookie。

4.1.3 通过js来反爬

普通的爬虫默认情况下无法执行js,获取js执行之后的结果,所以很多时候对方服务器会通过js的技术实现反爬。

4.1.3.1 通过js实现跳转来反爬

反爬原理:js实现页面跳转,肉眼不可见。

解决方法: 在chrome中点击perserve log按钮实现观察页面跳转情况。

在这些请求中,如果请求数量很多,一般来讲,只有那些response中带cookie字段的请求是有用的,意味着通过这个请求,对方服务器有设置cookie到本地。

4.1.3.2 通过js生成了请求参数

反爬原理:js生成了请求参数。

解决方法:分析js,观察加密的实现过程,通过js2py获取js的执行结果,或者使用selenium来实现。

4.1.3.3 通过js实现了数据的加密

反爬原理:js实现了数据的加密。

解决方法:分析js,观察加密的实现过程,通过js2py获取js的执行结果,或者使用selenium来实现。

4.1.4 通过验证码来反爬

反爬原理:对方服务器通过弹出验证码强制验证用户浏览行为。

解决方法:打码平台或者是机器学习的方法识别验证码,其中打码平台廉价易用,更值得推荐。

4.1.5 通过ip地址来反爬

反爬原理:正常浏览器请求网站,速度不会太快,同一个ip大量请求了对方服务器,有更大的可能性会被识别为爬虫。

解决方法:对应的通过购买高质量的ip的方式能够解决问题。

4.1.6 通过用户行为来反爬

反爬原理:通过浏览器请求数据,很多用户行为会在浏览器中是很容易实现或者无法实现.比如浏览器请求额外的图片地址,服务端进行记录,出现意味着不是爬虫(爬虫中不会主动请求图片)。

解决方法:通过获取数据的情况来观察请求,寻找异常出现的可能请求。

4.2 爬虫代码的建议

1.尽量减少请求次数:

1.能抓列表页就不抓详情页。 2.保存获取到的html页面,供查错和重复请求使用。

2.关注网站的所有类型的页面:

1.wap页面,触屏版的页面。 2.H5页面。 3.APP

3.多伪装:

1.动态的UA。 2.代理IP。 3.不使用cookie。

4.利用多线程分布式:

在不被ban的请求下尽可能的提高速度。

4.3动态HTML技术了解

JS:是网络上最常用的脚本语言。它可以收集用户的跟踪数据,不需要重载页面直接提交表单,在页面嵌入多媒体文件,甚至运行网页游戏。

jQuery:Query是一个快速、简洁的JavaScript框架,封装了JavaScript常用的功能代码。

Ajax:Ajax可以使网页实现异步更新,可以在不重新加载整个网页的情况下,对网页的某部分进行更新。

对搜索引擎不友好,对爬虫也不友好。

4.4Selenium

Selenium是一个Web的自动化测试工具,最初是为网站自动化测试而开发的,Selenium 可以直接运行在浏览器上,它支持所有主流的浏览器(包括PhantomJS这些无界面的浏览器,但是这种无界面浏览器不建议使用),可以接收指令,让浏览器自动加载页面,获取需要的数据,甚至页面截屏等。

selenium的安装

sudo pip3 install selenium

Chrome浏览器驱动的安装:

http://blog.csdn.net/huilan_same/article/details/51896672

1.下载对应版本的浏览器驱动

2.解压后得到驱动的可执行文件,将其拷贝到任意环境变量目录

3.echo $PATH查看环境变量路径

原理:python代码调用驱动,驱动调用浏览器。

4.4.1浏览器驱动对象

1.库的导入:

from selenium import webdriver

2.创建浏览器对象:

driver = webdriver.xxx()
# 访问一个url,只需要url即可
driver.get(url地址)
# driver已经保存了响应源码

使用dir(driver)查看方法

3.浏览器尺寸相关操作

maximize_window() # 最大化

4.浏览器的关闭操作:

close() # 关闭当前标签/窗口
quit() # 关闭所有标签/窗口

使用步骤

1.实例化浏览器驱动对象。

2.构建url发送请求。

# 创建一个浏览器对象
driver = webdriver.Chrome()
# 加载页面
driver.get("http://www.baidu.com/")
# 保存页面的快照
driver.save_screenshot("baidu.png")
# 定位和操作
driver.find_element_by_xpath("//*[@id="url"]/a[1]")

4.4.2常见对元素操作

获取响应源码:

dirver.page_source

获取cookie信息:

driver.get_cookies()

获取当前页面的标题:

driver.title

查看当前的url:

driver.current_url

定位百度页面的搜索框

kw = driver.find_element_by_id('kw')

往input表单中发送数据:

kw.send_keys('面向对象')

点击百度首页的百度一下:

su = driver.find_element_by_id('su')
su.click()

清空内容,然后再搜索其他内容:

kw.clear()
kw.send_keys('python中面向对象的多态')
su.click()

使用Selenium效率比较慢;而且太简单了,使用多了,爬虫技能都荒废了。

页面中包含部分文本『hao』的元素点一下:

hao = driver.find_element_by_partial_link_text('hao')
hao.click()

使用css选择器查找元素:

kw = driver.find_element_by_css_selector('#kw')
kw.send_keys('小闫笔记')

国内主流使用XPath,国外习惯使用css选择器。但是XPath效率高一点,正则是最高的。

4.4.3窗口与框架

XPath无法提取到Iframe框架里面的处理。

selenium标签页的切换

# 1. 获取当前所有的标签页的句柄构成的列表
current_windows = driver.window_handles
# 2. 根据标签页句柄列表索引下标进行切换
driver.switch_to.window(current_windows[0])

switch_to切换frame标签

iframe是html中常用的一种技术,即一个页面中嵌套了另一个网页,selenium默认是访问不了frame中的内容的,对应的解决思路是:

driver.switch_to.frame(frame_element)
# 切换到定位的frame标签嵌套的页面中
driver.switch_to.frame(通过find_element_by函数定位的frame、iframe标签对象)
# 利用切换标签页的方式切出frame标签
windows = driver.window_handles
driver.switch_to.window(windows[0])

4.4.4标签对象提取文本内容和属性值

find_element仅仅能够获取元素,不能够直接获取其中的数据,如果需要获取数据需要使用以下方法

获取文本 element.text

通过定位获取的标签对象的 text属性,获取文本内容

获取属性值 element.get_attribute("属性名")

通过定位获取的标签对象的 get_attribute函数,传入属性名,来获取属性的值

代码

from selenium import webdriver
driver = webdriver.Chrome('./chromedriver')
driver.get('http://www.itcast.cn/')

ret = driver.find_elements_by_tag_name('h2')
print(ret[0].text) # 

ret = driver.find_elements_by_link_text('小闫笔记')
print(ret[0].get_attribute('href'))

driver.quit()

4.4.5案例

4.4.5.1 58同城房屋出租信息采集
import time
from selenium import webdriver

# 需求:58同城,查找租房信息,多窗口的切换,获取标签的属性和值。
# 定义url地址
url = 'https://bj.58.com/'
driver = webdriver.Chrome()
driver.get(url)
# 查询租房信息
rent_list = driver.find_element_by_xpath('/html/body/div[3]/div[1]/div[1]/div/div[1]/div[1]/span[1]/a')
# 窗口句柄
print(driver.window_handles)
# 点击租房
rent_list.click()
print(driver.window_handles)

# 定位租房的窗口句柄,获取租房信息
# driver.switch_to_window()
driver.switch_to.window(driver.window_handles[-1])
# 获取租房信息
# print(driver.window_handles)
room_list = driver.find_elements_by_xpath('/html/body/div[5]/div/div[5]/div[2]/ul/li/div[2]/h2/a')

# 获取房屋租赁信息,text表示获取标签的文本,
for room in room_list:
    print(room.text,room.get_attribute('href'))

time.sleep(3)
# driver.quit()
driver.close()
4.4.5.2 模拟登陆QQ空间
import time
from selenium import webdriver

url = 'https://i.qq.com/'
driver = webdriver.Chrome()
driver.get(url)

# 定位到内部的iframe
login_frame = driver.find_element_by_id('login_frame')

# 切换到内部页面中
driver.switch_to.frame(login_frame)

# 定位点击账号密码登录标签
user_pswd = driver.find_element_by_xpath('//*[@id="switcher_plogin"]')

user_pswd.click()

# 定位账号input表单
u = driver.find_element_by_id('u')
u.send_keys('123456789')

# 定位密码框
p = driver.find_element_by_id('p')
p.send_keys('123456789')

# 定位登录按钮
login_button = driver.find_element_by_id('login_button')
login_button.click()

# 强制等待5秒
time.sleep(5)
# 隐式等待5秒
# driver.implicitly_wait(5)
driver.quit()

4.4.6cookie操作

获取浏览器中储存的cookies

driver.get_cookies()

删除指定cookie

driver.delete_cookie("CookieName")

删除所有的cookies

driver.delete_all_cookies()

4.4.7 页面等待

为什么需要等待?

答:浏览器进行渲染内容过多,网速的太慢,这个时候就可以设置一个等待时间,强制要求在时间内出现,否则报错。

强制等待

time.sleep(10)

隐式等待:就是简单地设置一个最大等待时间,单位为秒。所有的元素定位操作都会使用该时间

driver.implicitly_wait(10)

一般都使用隐式等待。

4.4.8 Selenium总结

1.Selenium应用场景

a.动态html页面请求,有很多数据是通过js运算得出。

b.简化模拟登陆,直接使用浏览器加载js。

2.如何使用

a.导入selenium相关的模块。

b.创建浏览器驱动对象。

c.使用驱动对象进行相关操作。

d.退出。

3.页面的等待:优先使用隐式等待,而后使用显示等待和固定等待。

4.和一般爬虫实现的区别

1.不需要写headers

2.不需要定义get_data()

5. Tesseract

Tesseract是一个将图像翻译成文字的OCR库(光学文字识别,Optical Character Recognition)

是根据明暗对比的。

1.安装引擎

a.Windows:https://code.google.com/p/tesseract-ocr/downloads/list

b.Linux平台:

sudo apt-get install tesseract-ocr

c.mac平台:

brew install tesseract

上传的图片如何转化为字符串?

1.在终端中调用:

tesseract test.jpg text

2.在python代码中使用:

安装

pip3 install pytesseract

使用

from PIL import Image
import pytesseract

# 打开图片文件创建图片对象
image = Image.open('test.jpg')
# 把图片识别成文字
image_text = pytesseract.image_to_string(image)
print(image_text)

小知识点

1.反爬手段不一定都添加在headers中。

2.一个html页面中一般是有一个body,但是也有页面中套页面的情况。

3.元素、标签、节点是一个意思。

总结

1、jsonpath的使用场景
a:多层字典嵌套的数据的快速提取。

2、xpath获取标签属性的语法
a:*/@href

3、xpaht获取标签文本的语法
a:*/text()

4、xpath查找特定的节点的语法
a://*[contains(text(),'下一页')]
b://*[@class='page']

5、lxml库如何使用
a:实例化etree对象
b:etree.HTMP(resp.content)
c:xpath语法、子节点可以再次使用xpath;

6、使用selenium发送请求,加载网页
a:实例化浏览器对象
b:构建url,发送请求

7、使用selenium获取浏览器中的数据的方法
a:text文本
b:get_attribute(属性)获取属性

8、使用selenium获取元素中定位的方法
a:xpath/id/tag_name/link/select/等

9、常见的反爬及对应的解决方法
a:headers----构建user_agent/referer/cookie
b:js----模拟js的执行过程,js2py把js代码下载到本地,用python执行、古老版本的user-agent、selenium
c:代理----proxies
d:爬取效率----自己测试目标网站
e:css字体----换不同的终端app
f:验证码----打码平台
g:用户行为和爬虫行为的区别----分析页面和爬虫请求数据的区别。

10、常见的打码平台
a、打码兔、云打码等。

优质文章推荐:

公众号使用指南

redis操作命令总结

前端中那些让你头疼的英文单词

Flask框架重点知识总结回顾

项目重点知识点详解

难点理解&面试题问答

flask框架中的一些常见问题

团队开发注意事项

浅谈密码加密

Django框架中的英文单词

Django中数据库的相关操作

DRF框架中的英文单词

重点内容回顾-DRF

Django相关知识点回顾

美多商城项目导航帖

项目重要技术点介绍

本文分享自微信公众号 - 全栈技术精选(Pythonnote)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-03-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券