Home >  > Scrpy的第七个爬虫Travelcity(调试及异步写入SQLite)

Scrpy的第七个爬虫Travelcity(调试及异步写入SQLite)

0

一、知识点:
1.urljoin
response.urljoin():将相对网址拼接成绝对网址。

比如:

url = response.body_as_unicode()
url = response.urljoin(url)

这样就能在url前拼接上https:

二、步骤
1.新建爬虫

scrapy startproject travalcity

cd travalcity

scrapy genspider travelspider travel.cn

2.新建Item (决定抓取哪些项目)

class TravalcityItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title = scrapy.Field()   #标题
    desc =scrapy.Field()   #简介

3.编写爬虫文件travelspider.py (决定怎么爬)

import scrapy
from scrapy.selector import Selector
from travalcity.items import *


class TravelspiderSpider(scrapy.Spider):
	name = "travelspider"
	allowed_domains = ["bytravel.cn"]
	start_urls = ['http://wap.bytravel.cn/view/index3480_list.html']

	def parse(self, response):
		for href in response.xpath("//ul[@id='titlename']/li/a/@href").extract():
			item=TravalcityItem()
			href = "http://wap.bytravel.cn"+href
			print(href)
			request=scrapy.http.Request(response.urljoin(href),callback=self.parse_desc)			
			request.meta['item']=item
			yield request


	def parse_desc(self,response):
	    item=response.meta['item']
	    item['title']=response.xpath("//h1").extract()
	    item['desc']=response.xpath("//article/div[3]").extract()
	    yield item

如果用xpath提取的url不是完整的域名,这时就需要使用urljoin进行拼接,传递任何url给urljoin可以将url的主域名取出来。

附另外一种写法:

from scrapy.http import Request
from urllib import parse  #python3用法

for post_url in post_urls:
   yield Request(url=parse.urljoin(response.url,post_url),callback=self.parse_detail)

全代码:

import scrapy
from scrapy.selector import Selector
from travalcity.items import *
from urllib import parse

class TravelspiderSpider(scrapy.Spider):
	name = "travelspider"
	allowed_domains = ["bytravel.cn"]
	start_urls = ['http://wap.bytravel.cn/view/index3480_list.html']

	def parse(self, response):
		for href in response.xpath("//ul[@id='titlename']/li/a/@href").extract():
			# href = "http://wap.bytravel.cn"+href
			print(href)
			request=scrapy.http.Request(url=parse.urljoin(response.url,href),callback=self.parse_desc)
			yield request


		 next_page=response.xpath("//nav[@id='list-page']/ul/li[last()-0]/a/@href").extract_first()
		 last_page=response.xpath("//nav[@id='list-page']/ul/li[last()-1]/a/@href").extract_first()
		 if last_page:
		 	next_page="http://wap.bytravel.cn/view/"+next_page
		 	yield scrapy.http.Request(next_page,callback=self.parse)

	def parse_desc(self,response):
	    item=TravalcityItem()
	    item['title']=response.xpath("//h1/text()").extract()[0]	    
	    item['desc'] = response.xpath("//article/div[3]/text()").extract()+response.xpath("//article/div/p/text()").extract()
	    tempItem = ""
	    for x in item['desc']:
	    	x =x.replace('\r\n','')
	    	x =x.replace('\r\n\r\n\r\n','')
	    	tempItem = tempItem + x
	    item['desc']= tempItem
	    item['province']=response.xpath('//div[@id="mainbao"]/a[2]/text()').extract()[0]
	    item['city']=response.xpath('//div[@id="mainbao"]/a[3]/text()').extract()[0]
	    yield item

4、测试
在“C:\Users\Kevin\travelspider\travalcity\travalcity>”文件夹下面执行下面的命令:

scrapy crawl travelspider -0 woodenrobot.csv
scrapy crawl travelspider -0 woodenrobot.json

注意,是o,不是0。
发现通过上面的输出,输出的csv乱码,输出的json也是编码不对。

这里只需要加上参数就可以了:

scrapy crawl travelspider -o woodenrobot.json -s FEED_EXPORT_ENCODING='utf-8'

5.修改pipilines.py (决定爬取后的内容怎么样处理)

import sqlite3
import pymysql.cursors

class TravalcityPipeline(object):
    def open_spider(self, spider):
        self.con = pymysql.connect(
        host='127.0.0.1',#数据库地址
        port=3306,# 数据库端口
        db='testscrapy', # 数据库名
        user = 'root', # 数据库用户名
        passwd='', # 数据库密码
        charset='utf8', # 编码方式
        use_unicode=True)

        self.cu = self.con.cursor()

    def process_item(self, item, spider):
        print(spider.name, 'pipelines')
        insert_sql = "insert into test (title,content) values('{}','{}')".format(item['title'], item['desc'])
        print(insert_sql)  # 为了方便调试        
        self.cu.execute(insert_sql)
        self.con.commit()
        return item

    def spider_close(self, spider):
        self.con.close()

6.修改settings.py (决定由谁去处理爬取的内容)
取消下面的代码的注释即可。

ITEM_PIPELINES = {
   'travalcity.pipelines.TravalcityPipeline': 300,
}

8.查看效果
这里最好使用Navicat来建立数据库,并将id设为“自动递增”以及为主键。

备注:下面还可以设置默认值。

可以看到已经成功地插入到数据库。

9. shell测试
直接执行以下命令:

scrapy shell "http://wap.xx.com"
response.xpath("//h1/text()").extract()[0]

10.去除'/r/n'
采集的结果中有许多'/r/n',在网上找了解决方案,说是可以使用normalize-space这个函数,但是我使用这个函数后,发现抓取的数据不全,比如明明一篇文章,未使用这个函数可以抓取到全部文件,使用了这个函数之后,后面有两段文章抓取不到。所以最后只能用字符串替换的方法来解决。

11、url函数
因为我是分块抓取的,所以另外写了一个函数,再使用start_urls = readUrl()将所有url加入到了起始url。

def readUrl():
	data = []
	for line in open("url.txt","r"): #设置文件对象并读取每一行文件
	    line = line.replace("\n","")
	    data.append(line)               #将每一行文件加入到list中
	return data

三、使用SQlite保存数据

1.新建sqlite数据库及数据表
最好的方法是使用Navicat,方法如下:

这里选择“新建SQlite3”,并将数据库文件的存储位置设置为执行“scrapy crawl xxx”命令的文件夹。

打开刚刚建立的数据库,开始新建数据表

这里完成各种字段的设置之后,navicat会提示你输入表格的名称。另外,在这里可以选择的SQlite的数据类型只有4种,大大少于Mysql的数据类型。

当然也可以通过命令来建立:

import sqlite3

# test.db is a file in the working directory
conn = sqlite3.connect("test.db")
c = conn.cursor()

# create tables
sql = '''create table student (id int primary key, name varchar(20), score int, sex varchar(10), age int)'''
c.execute(sql)

# save the changes
conn.commit()

# close the connection with the database
conn.close()

可以参考这里:https://www.cnblogs.com/lmei/p/5322502.html

2.修改pipeline

import sqlite3
 
 
class Sqlite3Pipeline(object):
 
    def __init__(self, sqlite_file, sqlite_table):
        self.sqlite_file = sqlite_file
        self.sqlite_table = sqlite_table
        
    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            sqlite_file = crawler.settings.get('SQLITE_FILE'), # 从 settings.py 提取
            sqlite_table = crawler.settings.get('SQLITE_TABLE', 'items')
        )
 
    def open_spider(self, spider):
        self.conn = sqlite3.connect(self.sqlite_file)
        self.cur = self.conn.cursor()
 
    def close_spider(self, spider):
        self.conn.close()
 
    def process_item(self, item, spider):
        insert_sql = "insert into travelcity (title,content,province,city) values('{}','{}','{}','{}')".format(item['title'], item['desc'],item['province'],item['city'])
        #insert_sql = "insert into {} (title,content,province,city) values('{}','{}','{}','{}')".format(self.sqlite_table,item['title'], item['desc'],item['province'],item['city'])
        self.cur.execute(insert_sql)
        self.conn.commit()        
        return item

3.修改settings.py文件

SQLITE_FILE = 'example.db'
SQLITE_TABLE = 'travelcity'

ITEM_PIPELINES = {
   'travalcity.pipelines.Sqlite3Pipeline': 300,
}

这时再运行爬虫就可以了。

可以参考:https://blog.csdn.net/weixin_34217711/article/details/90226081

4.在Navicat查询数据库的行数

5.关于数据丢失的问题
查看scrapy的报表发现丢失了许多数据,一直搞不懂是什么原因。

后来用两个列表页作测试,发现其中一个列表页丢失数据,加上twisted终于没有丢失数据了。

附最终代码:

import sqlite3
import pymysql.cursors
from scrapy import log
from twisted.enterprise import adbapi

class DbSqlitePipeline(object):
    def __init__(self):
        """Initialize"""
        self.__dbpool = adbapi.ConnectionPool('sqlite3',
                database='example.db',
                check_same_thread=False)
    def shutdown(self):
        """Shutdown the connection pool"""
        self.__dbpool.close()
    def process_item(self,item,spider):
        """Process each item process_item"""
        query = self.__dbpool.runInteraction(self.__insertdata, item, spider)
        query.addErrback(self.handle_error)
        return item
    def __insertdata(self,tx,item,spider):
        """Insert data into the sqlite3 database"""
        spidername=spider.name
        tx.execute(\
                "insert into worldcity(title,content,province,city) values (?,?,?,?)",(
                    item['title'],
                    item['desc'],
                    item['province'],
                    item['city'])
                )
        log.msg("Item stored in db", level=log.DEBUG)
    def handle_error(self,e):
        log.err(e)

参考:https://github.com/ritesh/sc/blob/master/scraper/pipelines.py

最终结果:

本文暂无标签

发表评论

*

*