四.使用Selenium模拟浏览器抓取淘宝商品美食信息.
1.相关工具介绍
Selenium是一个自动化测试工具,利用它可以驱动浏览器执行特定的动作,如点击、下拉等操作,同时还可以获取浏览器当前呈现的页面的源代码,做到可见即可爬。对于一些JavaScript动态渲染的页面来说,此种抓取方式非常有效。
PhanttomJS是无界面浏览器,因为老是开着浏览器,不方便.
Chrome是个浏览器
2.目标站点分析
本节中,我们要利用Selenium抓取淘宝商品并用pyquery解析得到商品的图片、名称、价格、购买人数、店铺名称和店铺所在地信息,并将其保存到MongoDB。
打开淘宝网:https://www.taobao.com/
搜索一个关键词:美食,看到非常好多吃的,我们现在就是要爬取这些内容.
打开淘宝页面,搜索商品,比如美食,此时打开开发者工具,截获Ajax请求,我们可以发现获取商品列表的接口,如图7-19所示。但是这个Ajax接口包含几个参数,参数不能直接发现其规律,如果要去探寻它的生成规律,也不是做不到,但这样相对会比较烦琐,所以如果直接用Selenium来模拟浏览器的话,就不需要再关注这些接口参数了,只要在浏览器里面可以看到的,都可以爬取。这也是我们选用Selenium爬取淘宝的原因

爬取的顺序是:需要模拟在输入框输入关键词,点击搜索按钮,获取首页的内容,模拟点击翻页,获得每页源代码,并分析商品信息,存储到MongoDB数据库.
4.1声明浏览器对象
4.1.1本次案例声明浏览器方法

from selenium import webdriver
browser = webdriver.Chrome()

Chrome浏览器会自动打开,如下截图,则说明驱动Chrome浏览器成功:

4.1.2声明浏览器拓展学习内容
Selenium支持非常多的浏览器,如Chrome、Firefox、Edge等,还有Android、BlackBerry等手机端的浏览器。另外,也支持无界面浏览器PhantomJS。
此外,我们可以用如下方式初始化:

browser = webdriver.Chrome()
browser = webdriver.Firefox()
browser = webdriver.Edge()
browser = webdriver.PhantomJS()
browser = webdriver.Safari()

这样就完成了浏览器对象的初始化并将其赋值为browser对象。接下来,我们要做的就是调用browser对象,让其执行各个动作以模拟浏览器操作。

4.2定义搜索的方法
4.2.1定义搜索的方法-search()
这里首先构造了一个WebDriver对象,使用的浏览器是Chrome,然后指定一个关键词,如美食.
等待加载时,我们使用了WebDriverWait对象,它可以指定等待条件,同时指定一个最长等待时间,这里指定为最长10秒。如果在这个时间内成功匹配了等待条件,也就是说页面元素成功加载出来了,就立即返回相应结果并继续向下执行,否则到了最大等待时间还没有加载出来时,就直接抛出超时异常。
比如,我们最终要等待商品信息加载出来,就指定了presence_of_element_located这个条件,然后传入了#q这个选择器,而这个选择器对应的内容就是搜索商品的输入框.
关于搜索美食操作,这里首先获取商品输入框,赋值为input,然后获取“确定”按钮,赋值为submit.
调用send_keys()方法将美食填充到输入框中,然后点击“确定”按钮(submit.click())即可。

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
browser = webdriver.Chrome()
wait = WebDriverWait(browser, 10)
def search():
    browser.get("https://www.taobao.com/")
    #等待浏览器的加载,需要一点时间,判断浏览器是否加载成功的方法,才进行下面的操作
    input = wait.until(
        EC.presence_of_element_located((By.CSS_SELECTOR, "#q"))#目标是输入框
    )
    submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#J_TSearchForm > div.search-button > button")))
    #按钮是可以点击的
    input.send_keys("美食") #操作动作
    submit.click()  
def main():
    search()    
if __name__=='__main__':
    main()

4.2.2selenium的拓展学习内容
官方学习资料:Selenium with PythonPython3网络爬虫开发实战7.1-Selenium的使用 显式等待
这里还有一种更合适的显式等待方法,它指定要查找的节点,然后指定一个最长等待时间。如果在规定时间内加载出来了这个节点,就返回查找的节点;如果到了规定时间依然没有加载出该节点,则抛出超时异常。示例如下:

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
browser = webdriver.Chrome()
browser.get('https://www.taobao.com/')
wait = WebDriverWait(browser, 10)
input = wait.until(EC.presence_of_element_located((By.ID, 'q')))
button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.btn-search')))
print(input, button)

这里首先引入WebDriverWait这个对象,指定最长等待时间,然后调用它的until()方法,传入要等待条件expected_conditions。比如,这里传入了presence_of_element_located这个条件,代表节点出现的意思,其参数是节点的定位元组,也就是ID为q的节点搜索框。
这样可以做到的效果就是,在10秒内如果ID为q的节点(即搜索框)成功加载出来,就返回该节点;如果超过10秒还没有加载出来,就抛出异常。
对于按钮,可以更改一下等待条件,比如改为element_to_be_clickable,也就是可点击,所以查找按钮时查找CSS选择器为.btn-search的按钮,如果10秒内它是可点击的,也就是成功加载出来了,就返回这个按钮节点;如果超过10秒还不可点击,也就是没有加载出来,就抛出异常。
运行代码,在网速较佳的情况下是可以成功加载出来的。

4.3获取总页面的页数内容-research()
4.3.1本次案例模拟翻页的方法
首先获取页数有多少页,点击美食搜索之后,下一步肯定是要等待页数加载出来,需要再加一个等待的判断,即
那么,怎样知道有没有跳转到对应的页码呢?我们可以注意到,成功跳转某一页后,页码都会高亮显示,如图7-25所示。total=wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.total")))
这里商品的搜索结果一般最大都为100页,要获取每一页的内容,只需要将页码从1到100顺序遍历即可,页码数是确定的。所以,直接在页面跳转文本框中输入要跳转的页码,然后点击“确定”按钮即可跳转到页码对应的页面。
使用wait时间过久会出现异常.因此增加try and except:

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
browser = webdriver.Chrome()
wait = WebDriverWait(browser, 10)
def search():
    try:
        browser.get("https://www.taobao.com/")
        #等待浏览器的加载,需要一点时间,判断浏览器是否加载成功的方法,才进行下面的操作
        input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#q")))#目标是输入框
        submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#J_TSearchForm > div.search-button > button")))
        #按钮是可以点击的
        input.send_keys("美食") #操作动作
        submit.click()
        total = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.total")))
        return total.text#返回内容
    except TimeoutException:
        return search()                                                                                    
def main():
    total=search()
    print(total)
if __name__=='__main__':
    main()

需要用正则表达式提取100
total=search()
total=int(re.compile(’(\d+)’).search(total).group(1))#打印出来可能是个字符串,所以要转义为整数

import re
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
browser = webdriver.Chrome()
wait = WebDriverWait(browser, 10)
def search():
    try:
        browser.get("https://www.taobao.com/")
        #等待浏览器的加载,需要一点时间,判断浏览器是否加载成功的方法,才进行下面的操作
        input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#q")))#目标是输入框
        submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#J_TSearchForm > div.search-button > button")))
        #按钮是可以点击的
        input.send_keys("美食") #操作动作
        submit.click()
        total = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.total")))
        return total.text#返回内容
    except TimeoutException:
        return search()                                                                                 
def main():
    total=search()
    total=int(re.compile('(\d+)').search(total).group(1))
    print(total)
if __name__=='__main__':
    main()

4.3.2获得总页面页数的学习内容
获取SELECTOR的目标值方法

4.4循环-遍历每页-next_page(page_number)
-刚才我们所定义的next_page(page_number)方法需要接收参数page,page代表页码。这里我们实现页码遍历即可.
其实现非常简单,只需要调用一个for循环即可。这里定义最大的页码数为100,range()方法的返回结果就是1到100的列表,顺序遍历,调用next_page(page_number)方法即可。
这样我们的淘宝商品爬虫就完成了,最后调用main()方法即可运行。

4.4.1本次循环的方法
1)首先查看淘宝页面,使用页面跳转的方法有两种,一是使用高亮的第1页,第2页,…下一页,而是直接输入第几页
-方法一 ,不推荐使用,具体原因如下:
这里不直接点击“下一页”的原因是:一旦爬取过程中出现异常退出,比如到50页退出了,此时点击“下一页”时,就无法快速切换到对应的后续页面了。此外,在爬取过程中,也需要记录当前的页码数,而且一旦点击“下一页”之后页面加载失败,还需要做异常检测,检测当前页面是加载到了第几页。整个流程相对比较复杂,所以这里我们直接用跳转的方式来爬取页面。

方法二,使用的方式是,直接在页面上写第几页.
2)页面输入框

input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.form > input")))

3)页面确定按钮

submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit")))

4)首先清除页面输入框的内容:
input.clear()#清除页码输入框的内容
完整的代码:

def next_page(page_number):
    print("正在翻页",page_number)
    try:
        input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.form > input")))#目标是输入框
        submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit")))
        input.clear()#清除页码输入框的内容
        input.send_keys(page_number)#输入页码
        submit.click()
        wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > ul > li.item.active > span"),str(page_number)))#判断是否翻页成功,通过判断当前的页数是否正确
        get_product()
    except TimeoutException:
        next_page(page_number)

4.5解析网页-商品列表-get_products()
接下来,我们就可以实现get_products()方法来解析商品列表了。这里我们直接获取页面源代码,然后用pyquery进行解析
首先,调用page_source属性获取页码的源代码,然后构造了PyQuery解析对象,接着提取了商品列表,此时使用的CSS选择器是#mainsrp-itemlist .items .item,它会匹配整个页面的每个商品。它的匹配结果是多个,所以这里我们又对它进行了一次遍历,用for循环将每个结果分别进行解析,每次循环把它赋值为item变量,每个item变量都是一个PyQuery对象,然后再调用它的find()方法,传入CSS选择器,就可以获取单个商品的特定内容了。
可以发现,它是一个img节点,包含id、class、data-src、alt和src等属性。这里之所以可以看到这张图片,是因为它的src属性被赋值为图片的URL。把它的src属性提取出来,就可以获取商品的图片了。不过我们还注意data-src属性,它的内容也是图片的URL,观察后发现此URL是图片的完整大图,而src是压缩后的小图,所以这里抓取data-src属性来作为商品的图片。
因此,我们需要先利用find()方法找到图片的这个节点,然后再调用attr()方法获取商品的data-src属性,这样就成功提取了商品图片链接。然后用同样的方法提取商品的价格、成交量、名称、店铺和店铺所在地等信息,接着将所有提取结果赋值为一个字典product,随后调用save_to_mongo()将其保存到MongoDB即可。
保存到MongoDB

4.5.1本次解析的方法
商品的代码结构,如下:
判断页面商品信息是否加载成功
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-itemlist .items .item “)))
获得页面所有选择的内容:
items=doc(’#mainsrp-itemlist .items .item’).items()
分析商品图片:
“image” :item.find(”.pic .img").attr(“src”)
解析商品价格
“price” :item.find(".price").text()
解析商品成交量
“deal” :item.find(".deal-cnt").text()[:-3]切片到倒数第三个
解析商品标题
“title” :item.find(".title").text()
解析商品店铺名称
“shop” :item.find(".shop").text()
解析商品店铺地址
“location” :item.find(".location").text()

完整的代码:

# -*- coding: utf-8 -*-
"""
Created on Sat Feb 10 18:33:26 2018
@author: Administrator
"""
import re
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
from pyquery import PyQuery as pq
browser = webdriver.Chrome()
wait = WebDriverWait(browser, 10)
def search():
    try:
        browser.get("https://www.taobao.com/")
        #等待浏览器的加载,需要一点时间,判断浏览器是否加载成功的方法,才进行下面的操作
        input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#q")))#目标是输入框
        submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#J_TSearchForm > div.search-button > button")))
        #确定按钮
        input.send_keys("美食") #操作动作
        submit.click()
        total = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.total")))
        get_product()
        return total.text#返回内容
    except TimeoutException:
        return search()
def next_page(page_number):
    try:
        input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.form > input")))#目标是输入框
        submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit")))
        input.clear()#清除页码输入框的内容
        input.send_keys(page_number)#输入页码
        submit.click()
        wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > ul > li.item.active > span"),str(page_number)))#判断是否翻页成功,通过判断当前的页数是否正确
        get_product()
    except TimeoutException:
        next_page(page_number) 
def get_product():
    wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-itemlist .items .item")))#判断商品信息是否加载成功
    html = browser.page_source#获得网页源代码
    doc=pq(html)
    items=doc('#mainsrp-itemlist .items .item').items()
    for item in items:
         product = {
                "image" :item.find(".pic .img").attr("src"),
                "price" :item.find(".price").text(),
                "deal" :item.find(".deal-cnt").text()[:-3],#切片到倒数第三个
                "title" :item.find(".title").text(),
                "shop" :item.find(".shop").text(),
                "location" :item.find(".location").text(),
                 }                    
         print(product)                                               
def main():
    total=search()
    total=int(re.compile('(\d+)').search(total).group(1))
    for i in range(2,total+1):
        next_page(i)
if __name__=='__main__':
    main()

4.5.2解析的学习内容

4.6保存在MongoDB

这里首先创建了一个MongoDB的连接对象,然后指定了数据库,随后指定了Collection的名称,接着直接调用insert()方法将数据插入到MongoDB。此处的result变量就是在get_products()方法里传来的product,包含单个商品的信息。

4.6.1本次保存在MongoDB的方法

新建一个配置文件叫untitled2.py,并输入以下代码

MONGO_URL ="localhost"#本地数据库
MONGO_DB="taobao"#数据库的名称
MONGO_TABLE="product"#数据库表的名称

在之前的文件引入mongodb相关数据

import re
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
from pyquery import PyQuery as pq
from untitled2 import *#引入mongodb的配置文件
import pymongo#引入mongodb所有的变量

在之前的文件声明mongodb相关信息

client=pymongo.MongoClient(MONGO_URL)
db=client[MONGO_DB]

在之前的文件定义mongodb方法

def save_to_mongo(result):
    try:
        if db[MONGO_TABLE].insert(result):
            print("存储到MONGODB成功",result)
    except Exception:
        print("存储到MONGODB失败",result)

完整的代码为:

import re
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
from pyquery import PyQuery as pq
from untitled2 import *#引入mongodb的配置文件
import pymongo
client=pymongo.MongoClient(MONGO_URL)
db=client[MONGO_DB] 
browser = webdriver.Chrome()
wait = WebDriverWait(browser, 10)
def search():
    try:
        browser.get("https://www.taobao.com/")
        #等待浏览器的加载,需要一点时间,判断浏览器是否加载成功的方法,才进行下面的操作
        input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#q")))#目标是输入框
        submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#J_TSearchForm > div.search-button > button")))
        #确定按钮
        input.send_keys("美食") #操作动作
        submit.click()
        total = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.total")))
        get_product()
        return total.text#返回内容
    except TimeoutException:
        return search()          
def next_page(page_number):
    try:
        input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#mainsrp-pager > div > div > div > div.form > input")))#目标是输入框
        submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > div.form > span.btn.J_Submit")))
        input.clear()#清除页码输入框的内容
        input.send_keys(page_number)#输入页码
        submit.click()
        wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR,"#mainsrp-pager > div > div > div > ul > li.item.active > span"),str(page_number)))#判断是否翻页成功,通过判断当前的页数是否正确
        get_product()
    except TimeoutException:
        next_page(page_number) 
def get_product():
    wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,"#mainsrp-itemlist .items .item")))#判断商品信息是否加载成功
    html = browser.page_source#获得网页源代码
    doc=pq(html)
    items=doc('#mainsrp-itemlist .items .item').items()
    for item in items:
         product = {
                "image" :item.find(".pic .img").attr("src"),
                "price" :item.find(".price").text(),
                "deal" :item.find(".deal-cnt").text()[:-3],#切片到倒数第三个
                "title" :item.find(".title").text(),
                "shop" :item.find(".shop").text(),
                "location" :item.find(".location").text(),
                 }                    
         print(product)
         save_to_mongo(product) 
def save_to_mongo(result):
    try:
        if db[MONGO_TABLE].insert(result):
            print("存储到MONGODB成功",result)
    except Exception:
        print("存储到MONGODB失败",result) 
def main():
    total=search()
    total=int(re.compile('(\d+)').search(total).group(1))
    for i in range(2,total+1):
        next_page(i)
    browser.close()#把浏览器关掉
if __name__=='__main__':
    main()

…见