python爬虫——用selenium和phantomjs对新浪微博PC端进行爬取(二)

。,。上一篇文章里我选择爬取简单的微博移动端,由于移动端构造简单,一般都优先爬取移动端,且因为是静态页面,我们可以直接使用xpath或者正则表达式搞定,但pc端结构就复杂得多,不能使用前面的方法。这篇文章我尝试使用selenium加phantomjs的组合,模拟人的操作来对指定微博的PC端进行爬取。

        这次我们选择的种子网页依旧是工商秘密微博,但爬取信息范围更广,我们需要爬虫不止拿到种子网页的信息,它的粉丝信息我们也希望获取到,包括粉丝的ID,地址,微博数量,关注人数,粉丝人数等,然后又以粉丝为节点循环下去,爬取到以工商秘密为网络中心点的大量信息。

        文章是我在学习网络爬虫课程期间为了加深知识的理解写的,多有不足,希望各位不吝指教。

        首先,这次的爬取分为两个部分,分别是对节点微博的信息爬取,对其粉丝的信息爬取。所以,我们构建两个爬虫程序,一个爬取的信息,一个爬取其粉丝的信息。获取到粉丝主页的url后又以其作为节点循环,两个爬虫之间用url填充的双端序列联系,粉丝爬虫将获取到的url加入序列,节点爬虫将其取出爬取。

       由于爬取链接极多,且为同一微博的粉丝,极可能抓取到重复的链接,这里我们使用布隆波过滤器进行网页链接的排重。

       

cur_queue=deque() 
download_bf=BloomFilter(1024*1024*16,0.01)
      首先构造一个存储下载链接的双端队列,然后设定好BloomFilter函数的碰撞概率为0.01

cur_driver=webdriver.PhantomJS(service_args=['--ssl-protocol=any', '--load-images=false']) 

follows_driver=webdriver.PhantomJS(service_args=['--ssl-protocol=any', '--load-images=false'])
  
cur_driver.set_window_size(1280,2400) #设窗口的大小

follows_driver.set_window_size(1280,2400)

  分别创建好cur_driver和follows_driver两个phantomjs浏览器对象,对他们的属性参数进行设置,由于我们使用这种爬取方式本身速度就很慢,所以把‘--load-images’属性设置为false,不下载图片,且phantomjs本身就是一个没有UI界面的浏览器,selenium则是浏览器自动化测试软件,我将selenium发出的命令理解为人操作phantomjs浏览器所发出的动作,而微博主页信息设置为需要滚屏下拉才能查看,且网速不好会出现重新加载的提示,针对这种情况,我们专门设置一个滚屏的函数。

def scroll_to_bottom():
    # 最多尝试 20 次滚屏
    print ("开始滚屏")
    for i in range(0,50):
       
        follows_driver.execute_script('window.scrollTo(0, document.body.scrollHeight)')
        html = follows_driver.page_source
        tr = etree.HTML(html)
        next_page_url = tr.xpath('//a[contains(@class,"page next")]')
        if len(next_page_url) > 0:
            return next_page_url[0].get('href')
        if len(re.findall('点击重新载入', html)) > 0:
            print ('滚屏失败了,请刷新')
            follows_driver.find_element_by_link_text('点击重新载入').click()
        time.sleep(1)

   在这个函数里,我们使用了selenium的script代码来执行向下滚屏的操作,用if语句来判断是否出现了重新加载的提示,如果有就点击它重新加载。

这里可以提一下,其实selenium自身有很多html和script的代码,可以用来查询修改网页标签属性等,不过速度实在太慢,但其优势在于可以对网页进行交互式操作,类似点击等,这是xpath,re等纯提取信息的库做不到的。

def go_next_page(driver):
    try:
        next_page = driver.find_element_by_xpath('//a[contains(@class, "page next")]').get_attribute('href')
        print ('进入下一页' + next_page)
        driver.get(next_page)
        time.sleep(3)
        return True
    except Exception:
        print ("找不到下一页了")
        return False

处理完向下滚屏后,网页的翻页行为也是需要我们自己定义的,我们依旧将翻页行为独立出来,定义为一个单独的函数,这里selenium查找标签使用的就是xpath的方法,定位到了下一页标签,获取到href然后传输给浏览器对象完成翻页行为。

def enqueurl(url) :
    try:
        
        md5v = hashlib.md5(str(url).encode('utf-8')).hexdigest()
        if md5v not in download_bf:
            print (url + '进入爬取队列')
            cur_queue.append(url)
            download_bf.add(md5v)
    except ValueError:
        pass

对于网页链接重复的问题,我们使用布隆波函数来处理,bloomfilter的参数我们在上面已经定义了,对于新抓取到的url我们需要判断它是否被爬取过,在进入双端序列前对它进行检查,这里要注意,我们必须先将url编码,否则无法将其哈希化,然后转化成16进制。使用if语句判断url是否已经存在于down_bf下载过的列表,如果是就直接pass,如果不是就将其加入双端队列和down_bf队列。
def login(username,password):
    print ("开始登录")
    cur_driver.get(url_home)
    print(len(cur_driver.page_source))
    follows_driver.get(url_home)
    
    time.sleep(4)

    print ("开始输入密码")
    cur_driver.find_element_by_id('loginname').send_keys(username)
    cur_driver.find_element_by_name('password').send_keys(password)  
    cur_driver.find_element_by_xpath('//div[contains(@class,"login_btn")][1]/a').click()
    
    print (len(cur_driver.page_source))

    follows_driver.find_element_by_id('loginname').send_keys(username)
    follows_driver.find_element_by_name('password').send_keys(password)  
    follows_driver.find_element_by_xpath('//div[contains(@class,"login_btn")][1]/a').click()

开始处理登陆的问题,这里就体现出了selenium模拟人操作的简便性,如果是直接用post的方式登陆,那就涉及到加密等相当麻烦的问题,而使用selenium就特别简单,我们只需要在网页中找到输入账号密码的标签,然后用selenium自带的send_keys对其进行复制就可以了,输完账号密码再找到登陆的标签,使用click()方法点击就可以成功登陆微博了。

def crawl():
    while True:    
        url=cur_queue.popleft()
        print(url)
        fech_user(url)
        
def fech_user(url_link):  
    print("开始爬取用户"+ url_link) 
    follows_driver.get(url_link)
    
    time.sleep(5)

    account_name = get_element_by_xpath(follows_driver,'//h1')[0].text
    follow_link = get_element_by_xpath(follows_driver,'//a[@clss="t_link S_text"]')[1].get('href')

    print('用户名' + account_name)
    print('粉丝列表' + follow_link)

    cur_driver.get(follow_link)  
    feeds=[]
    users=[]
    
    extract_feed(feeds)
    extract_user(users)

登录之后开始我们的爬虫程序,首先构造一个while循环,所要爬取的链接用.popleft()方法获取左边的url,然后传入进我们的处理函数fech_user,fech_user函数的任务是抓取到我们目标微博的ID和它粉丝列表的链接,此处将对粉丝信息的爬取和对节点用户的信息抓取分开处理,构造feeds和users两个空序列来存储我们所抓取到的信息,而extract_feed和extract_user两个函数则分别来提取信息。

def extract_feed(feeds): 
    print("开始提取微博发布信息")
    for i in range(0,20):
        scroll_to_bottom() 
        for element in follows_driver.find_elements_by_class_name('WB_detail')
            tried = 0
            while tried<3:
                try:
                    feed={}
                    feed['time'] = element.find_element_by_xpath('.//div[@class="WB_form S_txt2"]').text
                    feed['content'] = element.find_element_by_class_name('WB_text').text
                    feeds.append(feed)
                    
                    print(feed['time'])
                    print(feed['content'])
                    break
                except Exception:
                    tried += 1
                    time.sleep(1)

            if go_next_page(follows_driver) is False:
                return feeds
extract_feed函数用来提取我们指定的节点微博的信息,由于页面微博的信息设计到滚屏翻页的操作,这里我们使用一个for循环,最多滚屏20次,接下来对网页的信息标签进行分析,我们想要的数据时节点微博所发的微博内容和发该微博的时间,找到对应的标签,这里分别使用了xpath和class name来对标签进行定位,feed定义为字典类型,将节点微博所发的微博内容和时间作为键值对存储,然后放到我们先前定义的序列feeds中,由于网络的原因,加上这种方法本来加载也慢,我们需要经常使用time。sleep()方法来等待它。

def extract_user(users):  
    print("开始提取粉丝信息")
    for i in range(0,20):      
        scroll_to_bottom() 
for user_element in cur_driver.find_elements_by_xpath('//li[contains(@class,"follow_item")]'): tried = 0 while tried<8: try: user={} user['name'] = user_element.find_elements_by_xpath('//a[contains(@class,"S_txt1")]')[0].text user['follows'] = user_element.find_elements_by_xpath('//em[@class="count"]/a')[0].text user['fans'] = user_element.find_elements_by_xpath('//em[@class="count"]/a')[1].text user['blogs'] = user_element.find_elements_by_xpath('//em[@class="count"]/a')[2].text user['adress'] = user_element.find_elements_by_xpath('div[@class="info_add"]/span')[0].text users.append(user) print("----------------------------------------------------") print(user['name']+"关注"+user['follows']+ "粉丝"+user['fans']+"微博数"+user['blogs']+"地址"+user['adress']) except Exception: time.sleep(1) tried+=1 if go_next_page(user_crawler) is False: return user return user

开始构造extract_users函数,我们定义他来实现提取粉丝信息的功能,在前面我们获取到了节点用户粉丝链接后,将该链接传入到了extract——users函数,相当于我们的浏览器已经进入到了节点用户的粉丝网页中,这里同样用for循环进行滚屏20次的操作,点开粉丝的列表,我们想要获取到的信息有粉丝的年龄,地址,关注人数,他的粉丝数量,他所发的微博数,接下来我们使用开发者工具对网页中这些信息的元素进行分析,我们可以发现所有我们需要的信息都是存在li标签下,新浪应该是以li标签来对每个粉丝的信息进行存储,找到了对应的大标签,我们直接对其进行遍历,然后使用xpath的方法将其取出保存到我们定义的序列中。至此,重要的几个功能函数我们都已经定义完成,最后,我们开始构造程序的主函数。

def main():
    enqueurl(start_url)

   
    login(username,password)
    crawl()
    cur_driver.close()
    follows_driver.close()
    
main()
好了,主函数先调用enque函数,将种子网页加入到双端序列中,然后输入用户名和密码登陆新浪微博,在登陆之后运行爬虫程序,让他们自己循环抓取数据,最后记得使用.close()方法关闭我们创建的两个浏览器对象,由于phantomjs是独立于python的,所以如果我们不关闭它,它会一直在后台运行。

下面给出全部代码

#-*-coding:utf-8-*-
import hashlib
import threading
from collections import deque

from selenium import webdriver
import re
from lxml import etree
import time
from pybloom import BloomFilter

from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

user_agent= 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/60.0.3112.78 Chrome/60.0.3112.78 Safari/537.36' 

domain="weibo.com"
url_home="http://" + domain 

start_url="http://weibo.com/234687487?"      

cur_driver=webdriver.PhantomJS(service_args=['--ssl-protocol=any', '--load-images=false']) 

follows_driver=webdriver.PhantomJS(service_args=['--ssl-protocol=any', '--load-images=false'])
cur_driver.set_window_size(1280,2400)

follows_driver.set_window_size(1280,2400)

download_bf=BloomFilter(1024*1024*16,0.01) 

cur_queue=deque() 



def get_element_by_xpath(cur_driver, path):  #我们将xpath提取元素的方法单独列出一个函数,避免每次都写一长串代码去实现xpath功能
    tried = 0
    while tried < 6:
        html = cur_driver.page_source
        tr = etree.HTML(html)
        elements = tr.xpath(path)
        if len(elements) == 0:
            time.sleep(1)
            continue
        return elements



def extract_user(users):  
    print("开始提取粉丝信息")
    for i in range(0,20):
        for user_element in cur_driver.find_elements_by_xpath('//li[contains(@class,"follow_item")]'):
            tried = 0
            while tried<8:
                try:
                    user={}
                    user['name'] = user_element.find_elements_by_xpath('//a[contains(@class,"S_txt1")]')[0].text
                    user['follows'] = user_element.find_elements_by_xpath('//em[@class="count"]/a')[0].text
                    user['fans'] = user_element.find_elements_by_xpath('//em[@class="count"]/a')[1].text
                    user['blogs'] = user_element.find_elements_by_xpath('//em[@class="count"]/a')[2].text
                    user['adress'] = user_element.find_elements_by_xpath('div[@class="info_add"]/span')[0].text

                    print("----------------------------------------------------")
                    print(user['name']+"关注"+user['follows']+ "粉丝"+user['fans']+"微博数"+user['blogs']+"地址"+user['adress'])
                except Exception:
                    time.sleep(1)
                    tried+=1

            if go_next_page(user_crawler) is False:
                return user
        return user
                    
                    
            
        
    


def extract_feed(feeds):
    print("开始提取微博发布信息")
    for i in range(0,20):
        scroll_to_bottom()  #向下滚屏
        for element in follows_driver.find_elements_by_class_name('WB_detail'):
            tried = 0
            while tried<3:
                try:
                    feed={}
                    feed['time'] = element.find_element_by_xpath('.//div[@class="WB_form S_txt2"]').text
                    feed['content'] = element.find_element_by_class_name('WB_text').text
                    feeds.append(feed)
                    
                    print(feed['time'])
                    print(feed['content'])
                    break
                except Exception:
                    tried += 1
                    time.sleep(1)

            if go_next_page(follows_driver) is False:
                return feeds

                    
def scroll_to_bottom():
    # 最多尝试 20 次滚屏
    print ("开始滚屏")
    for i in range(0,50):
       
        follows_driver.execute_script('window.scrollTo(0, document.body.scrollHeight)')
        html = follows_driver.page_source
        tr = etree.HTML(html)
        next_page_url = tr.xpath('//a[contains(@class,"page next")]')
        if len(next_page_url) > 0:
            return next_page_url[0].get('href')
        if len(re.findall('点击重新载入', html)) > 0:
            print ('滚屏失败了,请刷新')
            follows_driver.find_element_by_link_text('点击重新载入').click()
        time.sleep(1)
    

def go_next_page(driver):
    try:
        next_page = driver.find_element_by_xpath('//a[contains(@class, "page next")]').get_attribute('href')
        print ('进入下一页' + next_page)
        driver.get(next_page)
        time.sleep(3)
        return True
    except Exception:
        print ("找不到下一页了")
        return False

    
def enqueurl(url) :
    try:
        
        md5v = hashlib.md5(str(url).encode('utf-8')).hexdigest()
        if md5v not in download_bf:
            print (url + '进入爬取队列')
            cur_queue.append(url)
            download_bf.add(md5v)
    except ValueError:
        pass


def crawl():
    while True:    
        url=cur_queue.popleft()
        print(url)
        fech_user(url)
        

def fech_user(url_link):  
    print("开始爬取用户"+ url_link) 
    follows_driver.get(url_link)
    
    time.sleep(5)

    account_name = get_element_by_xpath(follows_driver,'//h1')[0].text
    follow_link = get_element_by_xpath(follows_driver,'//a[@clss="t_link S_text"]')[1].get('href')

    print('用户名' + account_name)
    print('粉丝列表' + follow_link)

    cur_driver.get(follow_link)  
    feeds=[]
    users=[]
    
    extract_feed(feeds)
    extract_user(users)



def login(username,password):
    print ("开始登录")
    cur_driver.get(url_home)
    print(len(cur_driver.page_source))
    follows_driver.get(url_home)
    
    time.sleep(4)

    print ("开始输入密码")
    cur_driver.find_element_by_id('loginname').send_keys(username)
    cur_driver.find_element_by_name('password').send_keys(password)  
    cur_driver.find_element_by_xpath('//div[contains(@class,"login_btn")][1]/a').click()
    
    print (len(cur_driver.page_source))

    follows_driver.find_element_by_id('loginname').send_keys(username)
    follows_driver.find_element_by_name('password').send_keys(password)  
    follows_driver.find_element_by_xpath('//div[contains(@class,"login_btn")][1]/a').click()



def main():
    enqueurl(start_url)
    
    login(username,password)
    crawl()
    cur_driver.close()
    follows_driver.close()
    
main()
















没有更多推荐了,返回首页