ar_sitemapper 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +9 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Changelog.rdoc +28 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +98 -0
- data/LICENSE +24 -0
- data/README.rdoc +176 -0
- data/Rakefile +23 -0
- data/ar_sitemapper.gemspec +26 -0
- data/install.rb +5 -0
- data/lib/ar_sitemapper.rb +11 -0
- data/lib/sitemapper/active_record/builder.rb +54 -0
- data/lib/sitemapper/engine.rb +26 -0
- data/lib/sitemapper/generator.rb +100 -0
- data/lib/sitemapper/index.rb +84 -0
- data/lib/sitemapper/loader.rb +39 -0
- data/lib/sitemapper/pinger.rb +21 -0
- data/lib/sitemapper/sitemap.rb +22 -0
- data/lib/sitemapper/urlset.rb +76 -0
- data/lib/sitemapper/version.rb +5 -0
- data/lib/tasks/sitemapper.rake +26 -0
- data/templates/sitemaps.yml +34 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/mailers/.gitkeep +0 -0
- data/test/dummy/app/models/.gitkeep +0 -0
- data/test/dummy/app/views/layouts/application.html.erb +24 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +50 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +24 -0
- data/test/dummy/config/environments/production.rb +69 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +2 -0
- data/test/dummy/config/sitemaps.yml +36 -0
- data/test/dummy/db/schema.rb +74 -0
- data/test/dummy/lib/assets/.gitkeep +0 -0
- data/test/dummy/log/.gitkeep +0 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/support/app/models/foo_bar.rb +24 -0
- data/test/test_helper.rb +13 -0
- data/test/unit/engine_test.rb +19 -0
- data/test/unit/generator_test.rb +14 -0
- data/test/unit/loader_test.rb +21 -0
- data/test/unit/map_test.rb +9 -0
- data/test/unit/pinger_test.rb +11 -0
- data/test/unit/sitemapper_test.rb +22 -0
- data/test/unit/urlset_test.rb +9 -0
- metadata +221 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
require "rails"
|
2
|
+
require 'sitemapper/loader'
|
3
|
+
|
4
|
+
module AegisNet
|
5
|
+
module Sitemapper
|
6
|
+
|
7
|
+
class Engine < Rails::Engine
|
8
|
+
|
9
|
+
initializer 'ar_sitemapper.load_app_root' do |app|
|
10
|
+
AegisNet::Sitemapper.sitemap_file ||= File.join(app.root, "config", "sitemaps.yml")
|
11
|
+
end
|
12
|
+
|
13
|
+
initializer 'ar_sitemapper.hook_into_active_record' do
|
14
|
+
ActiveSupport.on_load(:active_record) do
|
15
|
+
::ActiveRecord::Base.send :include, AegisNet::Sitemapper::ActiveRecord::Builder
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
rake_tasks do
|
20
|
+
load 'tasks/sitemapper.rake'
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module AegisNet
|
2
|
+
module Sitemapper
|
3
|
+
class Generator
|
4
|
+
|
5
|
+
require "zlib"
|
6
|
+
|
7
|
+
VALID_GENERATOR_OPTIONS = [:file, :filename, :gzip, :xmlns]
|
8
|
+
|
9
|
+
# Generates an XML Sitemap file with +entries+. The output is written
|
10
|
+
# to a file if a filename is given or to stdout otherwise. Expects a
|
11
|
+
# block.
|
12
|
+
#
|
13
|
+
# === Parameters
|
14
|
+
# * +entries+: Enumerable to iterate through
|
15
|
+
# * +options+: an optional Hash. See below for supported options
|
16
|
+
#
|
17
|
+
# === Available Options
|
18
|
+
# * +:file+: full path to output file. If +:filename+ ends with .gz, Gzip-compression is activated
|
19
|
+
# * +:gzip+: force GZip compression if set to +true+
|
20
|
+
# * +:xmlns+: XML namespace to use, defaults to http://www.sitemaps.org/schemas/sitemap/0.9
|
21
|
+
#
|
22
|
+
# === Example
|
23
|
+
# sites = [
|
24
|
+
# { :url => "http://example.com/your/static/content1.html", :freq => "always", :prio => "1.0" },
|
25
|
+
# { :url => "http://example.com/your/static/content2.html", :freq => "monthly", :prio => "0.3" },
|
26
|
+
# ]
|
27
|
+
#
|
28
|
+
# AegisNet::Sitemapper::Generator.create(sites) do |site, xml|
|
29
|
+
# xml.loc site[:url]
|
30
|
+
# xml.changefreq site[:freq]
|
31
|
+
# xml.priority site[:prio]
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
def self.create entries, options = {}, &block
|
35
|
+
if block_given?
|
36
|
+
options.symbolize_keys!
|
37
|
+
options.assert_valid_keys(VALID_GENERATOR_OPTIONS)
|
38
|
+
xmlns = options[:xmlns] || "http://www.sitemaps.org/schemas/sitemap/0.9"
|
39
|
+
gzip = options[:gzip] || /\.gz$/.match(options[:file])
|
40
|
+
filename = options[:file] ? options[:file].gsub(/\.gz$/, '') : nil
|
41
|
+
|
42
|
+
if entries.size > 50_000
|
43
|
+
part_number = 0
|
44
|
+
entries.each_slice(50_000) do |part|
|
45
|
+
part_number = part_number.next
|
46
|
+
part_fn = filename.gsub('.xml', ".#{part_number}.xml")
|
47
|
+
|
48
|
+
create_one_sitemap(part, xmlns, part_fn, gzip, &block)
|
49
|
+
end
|
50
|
+
else
|
51
|
+
create_one_sitemap(entries, xmlns, filename, gzip, &block)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Infer full local path and sitemap filename by class name. Adds .xml.gz
|
57
|
+
#
|
58
|
+
# === Parameters
|
59
|
+
# * +klass+: class name
|
60
|
+
def self.default_filename(klass)
|
61
|
+
config = AegisNet::Sitemapper::Loader.load_config
|
62
|
+
File.join(config[:local_path], "sitemap_#{klass.to_s.underscore.pluralize}.xml.gz") if config[:local_path]
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.create_necessary_directories(filename)
|
66
|
+
FileUtils.mkpath( File.dirname(filename) )
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.create_one_sitemap(entries, xmlns, filename, gzip, &block)
|
70
|
+
write_one_sitemap(
|
71
|
+
generate_one_sitemap(entries, xmlns, &block),
|
72
|
+
filename,
|
73
|
+
gzip
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.generate_one_sitemap(entries, xmlns, &block)
|
78
|
+
xml = Builder::XmlMarkup.new(:indent => 2)
|
79
|
+
xml.instruct!
|
80
|
+
xml.urlset "xmlns" => xmlns do
|
81
|
+
entries.each do |entry|
|
82
|
+
xml.url { block.call(entry, xml) } rescue nil # TODO handle me / pass upwards
|
83
|
+
end
|
84
|
+
end
|
85
|
+
xml
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.write_one_sitemap(xml, filename, gzip)
|
89
|
+
# Either write to file or to stdout
|
90
|
+
if filename
|
91
|
+
create_necessary_directories(filename)
|
92
|
+
File.open(filename, "w") { |file| file.puts xml.target! }
|
93
|
+
Zlib::GzipWriter.open("#{filename}.gz") {|gz| gz.write xml.target! } if gzip
|
94
|
+
else
|
95
|
+
$stdout.puts xml.target!
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module AegisNet # :nodoc:
|
2
|
+
module Sitemapper # :nodoc:
|
3
|
+
# :doc:
|
4
|
+
|
5
|
+
class Index
|
6
|
+
|
7
|
+
attr_reader :host, :sitemaps
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
options.symbolize_keys!
|
11
|
+
@sitemaps = []
|
12
|
+
|
13
|
+
@config = AegisNet::Sitemapper::Loader.load_config
|
14
|
+
@host = @config[:default_host]
|
15
|
+
@file = File.join("#{@config[:local_path]}", @config[:index]["sitemapfile"])
|
16
|
+
|
17
|
+
@static = @config[:static]
|
18
|
+
@models = @config[:models]
|
19
|
+
@includes = @config[:index]["includes"]
|
20
|
+
|
21
|
+
# Validate all variables
|
22
|
+
raise(ArgumentError, "No filename specified") if @file.nil?
|
23
|
+
|
24
|
+
# Static Sitemap
|
25
|
+
if @static
|
26
|
+
sitemap_options = {:loc => @static["sitemapfile"], :lastmod => @static["lastmod"]}
|
27
|
+
@sitemaps << AegisNet::Sitemapper::Urlset.new(sitemap_options)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Include additional sitemaps
|
31
|
+
@includes.each do |sitemap|
|
32
|
+
sitemap_options = {:loc => sitemap["loc"] }
|
33
|
+
sitemap_options.merge!(:lastmod => sitemap["lastmod"]) if sitemap["lastmod"]
|
34
|
+
@sitemaps << AegisNet::Sitemapper::Sitemap.new(sitemap_options)
|
35
|
+
end
|
36
|
+
|
37
|
+
@models.each do |sitemap|
|
38
|
+
klass = sitemap.first.camelize.constantize
|
39
|
+
count = klass.count
|
40
|
+
|
41
|
+
order_opts = {}
|
42
|
+
order_opts = { :order => :created_at } if klass.column_names.include?("created_at")
|
43
|
+
lastmod = sitemap.last["lastmod"] || klass.last(order_opts).created_at
|
44
|
+
|
45
|
+
if count <= 50_000
|
46
|
+
sitemap_options = {:loc => sitemap.last["sitemapfile"], :lastmod => lastmod}
|
47
|
+
@sitemaps << AegisNet::Sitemapper::Urlset.new(sitemap_options)
|
48
|
+
else
|
49
|
+
1.upto( (count / 50_000.0).ceil ) do |part_number|
|
50
|
+
sitemap_options = {
|
51
|
+
:loc => sitemap.last["sitemapfile"].gsub("xml", "#{part_number}.xml"),
|
52
|
+
:lastmod => lastmod
|
53
|
+
}
|
54
|
+
@sitemaps << AegisNet::Sitemapper::Urlset.new(sitemap_options)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.create!(options = {})
|
61
|
+
index = self.new(options)
|
62
|
+
index.create!
|
63
|
+
end
|
64
|
+
|
65
|
+
def create!
|
66
|
+
xml = Builder::XmlMarkup.new(:indent => 2)
|
67
|
+
xml.instruct!
|
68
|
+
|
69
|
+
xml.sitemapindex "xmlns" => "http://www.sitemaps.org/schemas/sitemap/0.9" do
|
70
|
+
|
71
|
+
@sitemaps.each do |sitemap|
|
72
|
+
location = sitemap.loc.gsub(/^\//, '')
|
73
|
+
xml.sitemap do
|
74
|
+
xml.loc "http://#{@host}/#{location}"
|
75
|
+
xml.lastmod sitemap.lastmod.to_date if sitemap.lastmod rescue nil # TODO handle properly
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
File.open(@file, "w") { |file| file.puts xml.target! }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'sitemapper/generator'
|
2
|
+
require 'sitemapper/sitemap'
|
3
|
+
require 'sitemapper/urlset'
|
4
|
+
require 'sitemapper/index'
|
5
|
+
require 'sitemapper/pinger'
|
6
|
+
require 'sitemapper/active_record/builder'
|
7
|
+
|
8
|
+
module AegisNet
|
9
|
+
module Sitemapper
|
10
|
+
|
11
|
+
class Loader
|
12
|
+
# Loads the sitemap configuration from Rails.root/config/sitemap.yml
|
13
|
+
def self.load_config
|
14
|
+
# TODO verify file integrity
|
15
|
+
erb = ERB.new(File.read(AegisNet::Sitemapper.sitemap_file))
|
16
|
+
AegisNet::Sitemapper.configuration ||= HashWithIndifferentAccess.new(YAML.load(StringIO.new(erb.result)))
|
17
|
+
end
|
18
|
+
|
19
|
+
# Interprets +string+ as Ruby code representing a Proc and exectutes it.
|
20
|
+
#
|
21
|
+
# === Parameters
|
22
|
+
# * +string+: Ruby (Proc) code to be executed
|
23
|
+
# All other arguments will be passed to the Proc
|
24
|
+
#
|
25
|
+
# === Examples
|
26
|
+
# AegisNet::Sitemapper::Loader.proc_loader('Proc.new{"foo"}')
|
27
|
+
# => "foo"
|
28
|
+
#
|
29
|
+
# proc_str = 'Proc.new{|n| n}'
|
30
|
+
# AegisNet::Sitemapper::Loader.proc_loader(proc_str, "hello world")
|
31
|
+
# => "hello world"
|
32
|
+
def self.proc_loader(string, *args)
|
33
|
+
# TODO lambdas
|
34
|
+
eval(string).call(*args)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module AegisNet # :nodoc:
|
2
|
+
module Sitemapper # :nodoc:
|
3
|
+
# :doc:
|
4
|
+
|
5
|
+
class Pinger
|
6
|
+
|
7
|
+
require "net/http"
|
8
|
+
|
9
|
+
def self.ping!
|
10
|
+
config = AegisNet::Sitemapper::Loader.load_config
|
11
|
+
if config[:ping] and config[:default_host] and config[:index]
|
12
|
+
config[:pings].each do |ping_url|
|
13
|
+
url = ping_url + config[:default_host] + "/" + config[:index]["sitemapfile"]
|
14
|
+
Net::HTTP.get_response(URI.parse(url)) rescue nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module AegisNet # :nodoc:
|
2
|
+
module Sitemapper # :nodoc:
|
3
|
+
# :doc:
|
4
|
+
class Sitemap
|
5
|
+
attr_reader :lastmod, :loc
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
options.symbolize_keys!
|
9
|
+
options.assert_valid_keys(:changefreq, :lastmod, :loc, :priority)
|
10
|
+
@changefreq = options[:changefreq] || "weekly"
|
11
|
+
@lastmod = options[:lastmod]
|
12
|
+
@loc = options[:loc]
|
13
|
+
@priority = options[:priority] || 0.5
|
14
|
+
end
|
15
|
+
|
16
|
+
def changefreq(freq = nil); freq ? @changefreq = freq : @changefreq; end
|
17
|
+
def lastmod(lastmod = nil); lastmod ? @lastmod = lastmod : @lastmod; end
|
18
|
+
def loc(loc = nil); loc ? @loc = loc : @loc; end
|
19
|
+
def priority(prio = nil); prio ? @priority = prio : @priority; end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module AegisNet # :nodoc:
|
2
|
+
module Sitemapper # :nodoc:
|
3
|
+
# :doc:
|
4
|
+
|
5
|
+
class Urlset < AegisNet::Sitemapper::Sitemap
|
6
|
+
|
7
|
+
def create!
|
8
|
+
xml = Builder::XmlMarkup.new(:indent => 2)
|
9
|
+
xml.instruct!
|
10
|
+
|
11
|
+
xml.urlset "xmlns" => "http://www.sitemaps.org/schemas/sitemap/0.9" do
|
12
|
+
|
13
|
+
@sitemaps.each do |sitemap|
|
14
|
+
location = sitemap.loc.gsub(/^\//, '')
|
15
|
+
xml.url do
|
16
|
+
xml.loc "http://#{@host}/#{location}"
|
17
|
+
xml.lastmod sitemap.lastmod if sitemap.lastmod
|
18
|
+
xml.changefreq sitemap.changefreq
|
19
|
+
xml.priority sitemap.priority
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
File.open(@file, "w") { |file| file.puts xml.target! }
|
25
|
+
end
|
26
|
+
|
27
|
+
# Short-hand for Urlset#new and Urlset#create!
|
28
|
+
def self.create!(options = {})
|
29
|
+
sitemap = self.new(options)
|
30
|
+
sitemap.create!
|
31
|
+
end
|
32
|
+
|
33
|
+
# Generate Urlset sitemaps listed in sitemaps.yml
|
34
|
+
def self.build_all!
|
35
|
+
config = AegisNet::Sitemapper::Loader.load_config
|
36
|
+
|
37
|
+
# Generate sitemaps for AR Models dynamically by yml instructions
|
38
|
+
if config[:models]
|
39
|
+
config[:models].each do |ar_map|
|
40
|
+
opts = ar_map.last
|
41
|
+
klass = ar_map.first.camelize.constantize
|
42
|
+
|
43
|
+
build_opts = { :file => File.join("#{config[:local_path]}", opts["sitemapfile"]) }
|
44
|
+
build_opts.merge!( :conditions => opts["conditions"]) if opts["conditions"]
|
45
|
+
|
46
|
+
scope = opts["scope"].present? ? "#{opts["scope"]}" : :all
|
47
|
+
|
48
|
+
klass.build_sitemap scope, build_opts do |object, xml|
|
49
|
+
if opts["loc"].starts_with?("Proc")
|
50
|
+
xml.loc AegisNet::Sitemapper::Loader.proc_loader(opts["loc"], object)
|
51
|
+
else
|
52
|
+
xml.loc opts["loc"]
|
53
|
+
end
|
54
|
+
xml.lastmod object.updated_at.to_date
|
55
|
+
xml.changefreq opts["changefreq"] || "weekly"
|
56
|
+
xml.priority opts["priority"] || 0.5
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Find misc. sitemap data and generate a single static one
|
62
|
+
if config[:static]
|
63
|
+
entries = config[:static]["urlset"]
|
64
|
+
file = File.join("#{config[:local_path]}", config[:static]["sitemapfile"])
|
65
|
+
AegisNet::Sitemapper::Generator.create(entries, :file => file) do |entry, xml|
|
66
|
+
xml.loc entry["loc"]
|
67
|
+
xml.lastmod entry["lastmod"] if entry["lastmod"]
|
68
|
+
xml.changefreq entry["changefreq"] if entry["changefreq"]
|
69
|
+
xml.priority entry["priority"] || 0.5
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
namespace :sitemapper do
|
3
|
+
|
4
|
+
desc "Rebuilds all sitemaps"
|
5
|
+
task :rebuild => [:environment, :build_from_config]
|
6
|
+
|
7
|
+
desc "Notifies search-engines about the index-sitemap"
|
8
|
+
task :ping => :environment do
|
9
|
+
ActiveRecord::Migration.say_with_time "Pinging searchengines" do
|
10
|
+
AegisNet::Sitemapper::Pinger.ping!
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "Rebuild everything from config/sitemapper.yml"
|
15
|
+
task :build_from_config => :environment do
|
16
|
+
include Rails.application.routes.url_helpers
|
17
|
+
config = AegisNet::Sitemapper::Loader.load_config
|
18
|
+
default_url_options[:host] = config["default_host"]
|
19
|
+
|
20
|
+
ActiveRecord::Migration.say_with_time "Rebuilding sitemaps from #{AegisNet::Sitemapper.sitemap_file}" do
|
21
|
+
AegisNet::Sitemapper::Urlset.build_all!
|
22
|
+
AegisNet::Sitemapper::Index.create!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
default_host: "www.example.com"
|
2
|
+
local_path: <%= File.join Rails.root, "public", "sitemaps" %>
|
3
|
+
ping: true
|
4
|
+
|
5
|
+
index:
|
6
|
+
sitemapfile: "sitemap_index.xml"
|
7
|
+
includes:
|
8
|
+
-
|
9
|
+
loc: foo_sitemap.xml
|
10
|
+
static:
|
11
|
+
sitemapfile: "sitemapfoobar.xml.gz"
|
12
|
+
urlset:
|
13
|
+
-
|
14
|
+
loc: "http://www.example.com/static/page"
|
15
|
+
changefreq: weekly
|
16
|
+
priority: 0.1
|
17
|
+
-
|
18
|
+
loc: "http://www.example.com/important/static/page"
|
19
|
+
changefreq: daily
|
20
|
+
priority: 1.0
|
21
|
+
|
22
|
+
models:
|
23
|
+
foo_bar:
|
24
|
+
conditions: "foo = bar"
|
25
|
+
sitemapfile: sitemap_foo_bars.xml.gz
|
26
|
+
loc: Proc.new {|o| foo_bar_url(o) }
|
27
|
+
changefreq: weekly
|
28
|
+
priority: 0.7
|
29
|
+
|
30
|
+
pings:
|
31
|
+
- http://submissions.ask.com/ping?sitemap=
|
32
|
+
- http://www.google.com/webmasters/sitemaps/ping?sitemap=
|
33
|
+
- http://search.yahooapis.com/SiteExplorerService/V1/updateNotification?appid=YahooDemo&url=
|
34
|
+
- http://www.bing.com/webmaster/ping.aspx?siteMap
|