【企业级推荐系统实践】Scrapy爬虫爬取新浪数据

实现目标:

1. 通过scrapy框架爬取新浪五个分类主页的数据信息
2. 并实现持久化存储进mysql数据库

主要技术路线:

scrapy,selenium,webdriver,datetime,re,python的orm框架sqlalchemy


一、爬虫框架scrapy

【企业级推荐系统实践】Scrapy爬虫爬取新浪数据_第1张图片

cmd命令行
创建scrapy爬虫项目:scrapy startproject sina sina.com
cd进入 sina文件夹
创建爬虫spider:scrapy genspider sina1


二、编写spider

单个spider的整体结构:

1. __init()初始化函数
2. start_request()调度每一条url
3. parse()通过创建webdriver实例,使用Xpath语法定位元素,操作页面,爬取数据
4. parse_namedetail解析每一个大页面的中爬取出来的每一条子页面中的信息

1. __init()初始化函数:

		初始化url_list
		初始化page和flag参数
		初始化self.options——关于webdriver的一些参数
 def __init__(self,page=None,flag=None,*args,**kwargs):
        #一些参数初始化
        super(Sina1Spider,self).__init__(*args,**kwargs)
        self.page = int(page)         #爬多少页,代码里写不科学,作为函数参数传进来
        self.flag = int(flag)          #具体数值在哪里写入呢?在main.py文件的命令里写入

        self.start_urls = ['https://news.sina.com.cn/china/',
                           'https://ent.sina.com.cn/film/',
                           'https://ent.sina.com.cn/zongyi/',
                           'https://ent.sina.com.cn/star/',
                           'http://eladies.sina.com.cn/']

        self.option = webdriver.ChromeOptions()

        self.option.add_argument('headless')                                #不打开浏览器
        self.option.add_argument('no-sandbox')                              #不打开沙盒
        self.option.add_argument('--blink-setting=imagesEnabled=false')     #不要图片

2. start_request

    def start_requests(self):
        """
        从初始化方法的start_url中解析出单个url
        并通过yield关键字产生Request对象,通过callback参数回调给parse方法
        """
        for url in self.start_urls:
            yield Request(url=url,callback=self.parse)

3. parse函数

关键操作:
  1. 如何操作窗口向下滑动driver.execute_script("window.scrollTo(0,document.body.scrollHeight);")
  2. 所有item都使用PipeLines中创建的DataItem数据类型item = DataItem()
  3. 如何通过Xpath语法精准定位title和time元素title = driver.find_elements_by_xpath("//h2[@class='undefined']/a[@target='_blank']")
  4. 新闻的时间信息不相同,如何通过预处理将其转换为统一格式eachtime = eachtime.replace('今天',str(today.month)+'月'+str(today.day)+'日')
  5. 如何比较新闻发生的时间和today,yesterday?yesterday = (datetime.datetime.now() + datetime.timedelta(days=-1)).strftime("%Y-%m-%d")
  6. 如何将爬取的数据进一步解析?
    通过yield一个Request对象,其中有一个参数callback,该参数指定了Request对象该传给哪一个函数,与此同时也将href和item信息以参数形式穿进去yield Request(response.urljoin(href),meta={'name':item},callback=self.parse_namedetail)
    def parse(self, response):
        """
        解析内容
        :param response:
        :return:
        """
        driver = webdriver.Chrome(chrome_options=self.option)
        driver.set_page_load_timeout(30)
        driver.get(response.url)
        #以上是webdriver,打开浏览器的一些设置,知道打开哪个网页

        # 【核心操作】 双重for循环:
        #           第一个for循环次数为page的页数:
        #               每一页不会一下子全部加载出来,有一个向下滑动加载更多的效果
        #           第二个for循环次数为每页中小新闻标题的个数:
        #               每次生成一个针对这个小新闻标题的Request对象,通过callnack参数传给下一个函数parse_namedetail

        for i in range(self.page):
            while not driver.find_element_by_xpath("//div[@class='feed-card-page']").text:      #直到翻页的按钮出现
                driver.execute_script("window.scrollTo(0,document.body.scrollHeight);")         #滚动条往下滑动网页
            title = driver.find_elements_by_xpath("//h2[@class='undefined']/a[@target='_blank']")
            time = driver.find_elements_by_xpath("//h2[@class='undefined']/../div[@class='feed-card-a feed-card-clearfix']/div[@class='feed-card-time']")

            #不再细分,直接用DataItem
            for i in range(len(title)):
                eachtitle = title[i].text
                eachtime = time[i].text
                item = DataItem()
                if response.url == "https://ent.sina.com.cn/zongyi/":
                    item['type'] = 'zongyi'
                elif response.url == "https://news.sina.com.cn/china/":
                    item['type'] = 'news'
                elif response.url == "https://ent.sina.com.cn/film/":
                    item['type'] = 'film'
                elif response.url == "https://ent.sina.com.cn/star/":
                    item['type'] = 'star'
                elif response.url == "http://eladies.sina.com.cn/":
                    item['type'] = 'nvxing'

                item['title'] = eachtitle
                item['desc']  = ''
                href = title[i].get_attribute('href')
                today = datetime.datetime.now()
                eachtime = eachtime.replace('今天',str(today.month)+'月'+str(today.day)+'日')       #把'今天xx:xx'格式转换成年月日的标准形式

                #在首页对日期进行处理,日期的显示有不同情况,分类讨论
                if '分钟前' in eachtime:
                    minute = int(eachtime.split('分钟前')[0])
                    t = datetime.datetime.now() - datetime.timedelta(minutes=minute)
                    t2 = datetime.datetime(year=t.year,month=t.month,day=t.day,hour=t.hour,minute=t.minute)

                elif '年' not in eachtime:
                    eachtime = str(today.year) + '年' + eachtime
                    t1 = re.split(r'[日月年:]',eachtime)
                    t2 = datetime.datetime(year=int(t1[0]),month=int(t1[1]),day=int(t1[2]),
                                           hour=int(t1[3]),minute=int(t1[4]))

                item['times'] = t2

                #判断跑全量还是跑增量,1为增量,0为全量
                if self.flag == 1:
                    today = datetime.datetime.now().strftime("%Y-%m-%d")
                    yesterday = (datetime.datetime.now() + datetime.timedelta(days=-1)).strftime("%Y-%m-%d")
                    if item['times'].strftime("%Y-%m-%d")  < yesterday:            #昨天之前的内容不要
                        driver.close()
                        break
                    elif yesterday <= item['times'].strftime("%Y-%m-%d") < today:
                        yield Request(response.urljoin(href),meta={
     'name':item},callback=self.parse_namedetail)
                else:
                    yield Request(response.urljoin(href),meta={
     'name':item},callback=self.parse_namedetail)
                    # 生成了一个Request对象,通过参数callback回调给parse_namedetail函数

            #点击下一页
            try:
                driver.find_element_by_xpath("//div[@class='feed-card-page']/span[@class='pagebox_next']/a").click()
            except:
                break

4. parse_namedetail函数解析子网页

关键操作:
  1. 创建cssselector,通过selector调用Xpath语法提取元素
  2. map(func,Iterator) map函数将迭代器iterator放入func更新成新的迭代器
  3. 最后将yield item ,由pipeline接收,并由pipeline里面的process_item方法进行数据持久化操作
  def parse_namedetail(self, response):
        """
        从每一个单独的新闻页面中解析出time、desc、item
        :param response:
        :return:
        """
        #css selector选取元素
        selector = Selector(response)
        item = response.meta['name']
        desc = selector.xpath("//div[@class='article']/p/text()").extract()

        #处理desc
        desc = list(map(str.strip,desc))
        item['desc'] = ''.join(desc)

        yield item

三、Items.py创建一个通用的DataItem

用来接收spider里面的item


import scrapy

class DataItem(scrapy.Item):
    title = scrapy.Field()
    desc = scrapy.Field()
    times = scrapy.Field()
    type = scrapy.Field()



四、pipelines

关键操作:
  1. 通过sqlalchemy这个orm框架创建接收item信息的数据类型
Base = declarative_base()

class Data(Base):
        __tablename__ = 'data'
        id = Column(Integer(), primary_key=True)
        times = Column(DateTime)
        title = Column(Text())
        content = Column(Text())
        type = Column(Text())
  1. __init方法里初始化数据库引擎
# 初始化数据库引擎,并将其绑定
        self.engine = create_engine('mysql+pymysql://root:123456@localhost:3306/sina', encoding='utf-8')
        Base.metadata.create_all(self.engine)
        self.DBSession = sessionmaker(bind=self.engine)

3.提交

		session = self.DBSession()
        session.add(new)
        session.commit()

完整代码

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column,create_engine,Text,DateTime,String,Integer
from sqlalchemy.orm import sessionmaker

Base = declarative_base()

class Data(Base):
        __tablename__ = 'data'
        id = Column(Integer(), primary_key=True)
        times = Column(DateTime)
        title = Column(Text())
        content = Column(Text())
        type = Column(Text())

class SinaPipeline:
    def __init__(self):
        # 初始化数据库引擎,并将其绑定
        self.engine = create_engine('mysql+pymysql://root:123456@localhost:3306/sina', encoding='utf-8')
        Base.metadata.create_all(self.engine)
        self.DBSession = sessionmaker(bind=self.engine)

    def process_item(self, item, spider):
        new = Data()
        new.title = item['title']
        new.times = item['times']
        new.content = item['desc']
        new.type = item['type']

        session = self.DBSession()
        session.add(new)
        session.commit()
        return item

五、执行

main文件
需要传入spider文件init方法里要求的两个arg
-a page=10 -a flag=0格式传入,注意空格

from scrapy import cmdline

cmdline.execute('scrapy crawl sina1 -a page=10 -a flag=0'.split())

你可能感兴趣的:(爬虫,推荐系统,python)